KDOC 59: ECSを使ってRPGを作る
この文書のステータス
- 作成
- 2024-11-10 貴島
- レビュー
- 2024-11-11 貴島
WIP プロジェクトのステータス
プロジェクトは進行中である。
概要
Entity Component Systemを使って、サンプルゲームを作る。
↑実際のゲーム画面。ウィンドウにフォーカスして方向キーで操作できる。
- リポジトリ。kijimaD/ruins
↑実際のゲーム画面。クリックもしくはエンターでページ送り。
- リポジトリ。kijimaD/nova
スケジュール
締め切りがなければ、取捨選択できない。
- 10月リリース
要素一覧
各要素を縦に深掘りして各要素を完成させていくときだ。
- オークション
- 購入(稼ぐ動機)
- 戦闘
- プレイヤーのビルド
- プレイヤー生活
- 睡眠
- 料理
- スキル
- 健康と病気
- 世界の寒さと体温
- UI
- コンテンツ量
- アイテム
- 合成ガチャ
- マップバリエーション
- ストーリー
- 難易度曲線
- クエスト
- チュートリアル・ヘルプ
- 実績
- ストアページ
Tasks (MUST)
TODO 地図に道を表示する
道路。
TODO 収納にアイテムが入っているかわからない
調べにくい。
TODO 地図ステートをモーダルにする
黒背景がほかと一貫してない。
TODO アイテム説明に「これは食べられる」「これは読める」などを表示する
どのアクションが使えるか知れるようにする。
TODO 固定物はビルボードをやめる
- 固定物がビルボードだと違和感がある
- 本棚とか冷蔵庫
- アイテムと区別しにくい
- 方向を固定させ、薄いポリゴンにする
- 固定物とアイテムの区別を考えるとき
Tasks (SHOULD)
TODO activity の中断理由をログに出す
キャンセルされた理由をフィードバックする。
TODO replay でステート確認する
画像差分だけでなくステート遷移がうまくいっているかの確認もできるだろう。
TODO オブザーバビリティを検討する
- 余地があるのだろうか
TODO クラッシュログをファイル保存してリリース後に検証できるようにする
配布後。
- スタックトレースをファイルなどに残す
TODO キューブのスロットを追加する
- 工具や調理器具などの、行動を拡張/強化するスロットメニューを作る。そこにアイテムが入っていて、キューブの近くにいると利用できる
- スロットに入れられる空間に制約があり、やりくりする要素
- キューブの強化要素
- 溶接機などの重いものは、スロットで素材を消費してクラフトする。これによって、建設機能を作らずにパワーアップを追加できる
- ただ、ローグライク世界に合致するのかどうか
- キューブメニューのなかにオークションがある
TODO 生食できないものを直接食べられてしまう
- 肉とか海産物は、生で食べられるべきではない
- 調理によって、回復量が大きく変わるべきものがある
TODO 運転中に残燃料を表示する
残りがわかりにくい。
TODO 時間帯が切り替わるのが早すぎる
何もしてないのに、すぐ夜になる。
TODO 建物のなかに敵がいない
安全で、緊張感がない。建物の中こそリターンがあるのでリスクも存在するべきである。
TODO アイテムが散らばっていて不自然
- 適切な棚に入っているべきである
- 食べ物が多すぎて変
- 生鮮物が見合った腐敗速度になっているか
TODO 極限状態でキューブを押すとターンが無限ループする
飢餓状態でキューブを押すと、永遠に行動できなくなる。
TODO 地図ステートがヘルプキー対応してない
- 表示がほかと異なる
- ヘルプキー対応してない
TODO README でゲーム内容を説明する
何か短い言葉で説明する。
TODO チュートリアルの表示
キーを出せばよいだろうか。キューブ周りがわかりにくいんだよな。
TODO 道の真ん中に施設があることが多い
道はつながっていないと変だ。
TODO ゲージのとなりにアイコンを表示する
- HP と体温ゲージがなにかわかりにくい
TODO ストーリーメッセージの書き直し
以前のままであるから、書き直す必要がある。
- ゲーム内
- Steam ストアページ
TODO transfer系は個数を指定できるようにする
スタックしている場合、デフォルトは全数。数ウィンドウを出す。
TODO アイテム配置場所の調整
- 食品が床に置いてあって不自然だ
TODO 個数入力ポリシーを決定する
複数アイテムあるときの対応がバラバラなので統一する。
TODO メニューを重複しない透明にする
現状、メニュー同士が重複しても透過しない。メニューが複数重なっても一番後ろのゲーム背景が透過するとかっこいい。
- 底の画面 + もっとも上の画面を表示する
- 「底」をどう定義するかが難しい
- アクティブなメニューだけ半透明にする
TODO 一貫してない色定義をまとめる
バラバラに定義していて、微妙に違う色だったりする。テーマみたいに全体定義して使うのが望ましい。
- message window の背景色が異なる
TODO メインメニューから開始したときに処理待ち時間に真っ黒な画面になる
とくにWASM版で発生する。マシンによってはネイティブ版でも発生するだろう。
TODO 家具は通行可能にして移動コストをつける
- 背の低い家具は移動コストが高いが通行可能にして、家屋内でのストレスを下げる
TODO GitHub Actions で parallel 命令を使って書き直す
共通処理をまとめてやれてエコになる。
TODO 状態変化関数をまとめる
- Propを取得、配置、などで専用関数をまとめて配置してわかりやすくする
https://github.com/kijimaD/ruins/blob/305f34bd82cae90fa0d0274862836e7703c7b9f4/internal/activity/pickup.go#L167
func (pa *PickupActivity) collectProp(actor ecs.Entity, world w.World, propEntity ecs.Entity) error {
TODO イベントをプレーンテキストで書く
イベントを関数で書くのがわかりづらく感じてきた。
ただ、テキストを表示するだけでなく遷移とかアイテム追加とかあるのを考えると難しい。
せめて、テキストだけまとめるとか変数にまとめられるとわかりやすいだろう。
TODO 天候を追加する
- 雷
- 雨
TODO 装備カスタムを追加する
- 装備カスタム。追加性能をつけたりする
TODO 灯りを有限にする
- 燃料を消費するようにする
- 灯りは性能が異なる
TODO フロアを改変できるようにする
- 爆弾
- つるはし
TODO 生態系関係を追加する
- ロボットはすべての生物系と敵対する
- 同士討ちさせる戦略が出る
TODO データのバリデーション
現状、設定値を実行時にしか確かめていない。事前に検証できるとよい。
- 文字数制限
- データの正しさ
- 値域
TODO 設定値のホットリロード
可能かは不明。
現状ゲームパラメータを変更したときに再コンパイルが必要だが、これは手間である。どうにかできないか。
TODO 素材概念を追加する
- 多くのアイテムは「分解」できる
- たとえばダウンは羽毛、ポリエステルで構成される
- ダウンそのものが断熱性能を持つのではなく、羽毛の性質が軽くて温かい性質を持つ
- 2つの素材によって、燃えやすく脆いのが決定する
- 分解すると2つの素材が出る
こういった素材や性質ベースの決定は、奥深く感じられるので積極的に取り入れていく。
- ただ、コントロールがしにくい問題や装備ごとの違いをもたせにくい問題がある…だろうか
- 温度性能に関しては画一的でよさそう
TODO 複数ターンのAP消費を統一する
- 読書とリロードは方式が異なる
- 解体の残りターン形式は途中でキャラのステータスが変動したときに対応できない。APを引き算していく方法でなければ正確ではない
TODO デバッグ設定を調整する
- 戦闘時のHP表示
- フィールド上でのエンカウント無視
TODO 合成のレアリティスコア
性能にスコアをつけ、結果的に出来上がったものに対してレアリティランクをつけるとよさそう。
TODO セーブデータの互換性を考える
- 今の仕組みだと、値を変えたりしたときに既存セーブデータに反映されない
- 現状は最初にスポーンしたデータをそのまま使い続ける。値に更新があっても、再びスポーンしなければ変わることはない
- セーブデータにバージョンを保存しておいて弾く、警告を出すだけでよいかも
TODO 敵AIを改善する
- 脅威評価の数値化 現状は最近接の敵1体しか見ていない。複数の敵がいるとき「囲まれている」判定ができず、撤退判断が遅れる。CDDAのように敵味方の数・距離からスコア算出すれば、撤退ポリシーが賢くなる
- 動的障害物の扱い分離 現状はプレイヤーを `BlockPass` で静的にブロックしている。CDDAのように「経路探索時はキャラを無視、移動実行時に衝突処理」にすると、一時的な障害物(敵同士のすれ違いなど)に強くなる
- 追従距離の状況可変化 CDDAは「プレイヤーが見えない→距離2」「階段→距離1」のように状況で変える。ruinsも戦闘中は距離を詰める、安全なら距離を広げるなどができる
将来的に価値がある改善。
- 移動コストの差別化 地形ごとにコスト差をつけるならBFS→A*への移行が必要。現状の均一マップでは不要
- アイテム評価の3段階化 CDDAの `can_take` × `wants_take` × `would_take`。装備の質やニーズに基づく判断
TODO ステート可視化する
デバッグ用に、ステートにゴミが残ってないかチェックする。
TODO スロット番号を型にする
1~5ということを型で示す。
type WeaponSelection struct {
// Slot は選択中の武器スロット番号。1から5
Slot int
}
というか番号はつけずに配列でよくないだろうか。
TODO チャンクごとのplannerを実装する
いまはオーバーワールドに1種類の1プランナーしかない。
TODO ダンジョン移動したときはスプラッシュを表示する
state が切り替わったときしか表示されなくなっている。
- ダンジョンにランダム生成の名前をつけるとよさそう
TODO 部屋の種類の一覧を標準規格で調査する
規格から、部屋を作れる。
TODO 窓を視線が通るようにする
現状は壁の上に窓を置いているため、窓を壊すと壁が現れるだけになる。
TODO メッセージが飛び出して表示される
文字数が長くて飛び出しているものがある。
- description
- メッセージ
検知、あるいは強制改行できないだろうか。
TODO やる気を追加する
- やる気が上がると能力にバフがかかる
- キャラクタによって、何を好むかは異なる
- やる気は下がるが栄養は多い、などのトレードオフが取れる
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で失敗するな
- 手元で動かすと発生しないのはなぜなんだろうな
DONE コンポーネントを移動する
移動する。
- メニューを透過できないな。真っ暗になる。fieldだとできるのはなぜだろう
- スプライトを表示できない。何も表示されない
- geoMで位置を指定してないせいだった
DONE 影描画をsystemでやる
元の仕組みとは異なるため、調整が必要。
- 真っ黒になるな
- レイも見えないので、間違ってるようだ
- 視点と同じ位置にあるために、全部真っ暗になっている説
- 外すと、いくつかのrayがおかしくなっていて失敗する
- 特定の点がおかしいようだ
- 外周の壁がなくて、交点がないrayがあったせいだった
DONE フィールド描画をECS化する
- まず影は置いておく
- プレイヤー部分を色塗りできた
- スプライトを表示するにはどうすればいいのだろう
func createWarpNextEntity(componentList *loader.EntityComponentList, gameSpriteSheet *ec.SpriteSheet, line, col int) {
componentList.Engine = append(componentList.Engine, loader.EngineComponentList{
SpriteRender: &ec.SpriteRender{SpriteSheet: gameSpriteSheet, SpriteNumber: warpNextSpriteNumber},
Transform: &ec.Transform{},
})
componentList.Game = append(componentList.Game, GameComponentList{
Warp: &gc.Warp{Mode: gc.WarpModeNext},
GridElement: &gc.GridElement{Line: line, Col: col},
})
}
- スプライトシートは、起動時にtomlがロードされResourceの構造体に入れられる。ステート名をキーにして別々になっている
- フィールドは “field” に、まとめて入っている
DONE とりあえずの背景を設定する
Menuをなくしたとき、非表示にしてた。戻す。
DONE 壁を通過できないようにする
- rectの中に入れないようにする。
- 線として持っておく必要はなくて、点4つだけを持っていればいい
- ただそれだとステージの壁を自動生成しづらくなるのだが…
- 範囲内のオブジェクトに対して衝突判定する
- すべてのフィールド上オブジェクトは、中心のx, yをもつ。その中心と合わせてスプライトを配置し、4つの点を衝突判定に使う
- この方式はキャラクターや敵、飾りオブジェクトには良いが、壁を配置するときには不適当だ。1点ではなくて、4点を指定したいだろう
- ピクセル単位で配置できなくてもよくて、50ピクセルごととかでよさそう。こうするとテキストファイルでマップを作成できる。壁は50ピクセル正方形で囲って表現する
- オブジェクト数が大変なことになりそうな懸念。視界をもつキャラごとにあるわけで。とはいえ、視界を限ればそんなでもなさそうな気も。オブジェクト数は多くなるが、そういうもんだろう。
- 2次元配列の問題に落とし込めることは重要に見える
- 壁は近傍8タイルで座標を変化させればいい。縦に並んでいれば横は細くするみたいな
spawnWall(1, 1) // (1, 1)に壁を生成
spawnPlayer(2, 2)
spawnObject("岩", 20, 20)
spawnObject("花", 30, 30)
spawnWarp(40, 50)
############ # # # # / # ############
- 現状のraycastの構造を、1点だけ保持するように修正しようとしているが、よくわからない
- プレイヤーや壁の描画を先にやればよいのでは。影は一度忘れる
DONE 壁テクスチャが影で隠れないようにする
今は影に隠されている。影側で表示しようとすると、影の中にあるときもテクスチャが表示されてしまって微妙。
- 視界側でブレンドするとよさそう。いやでも、視界に入れば影になっていても見えてしまうのは同じか
- テクスチャ 影 ほかのテクスチャ 影
- テクスチャの描画優先度が距離によって変わればいいのかな。大変そうだ
- rayブレンド時に、スプライトも追加してやればいいかな
- ぐちゃぐちゃになったのでいったんもどす
// rayが命中しているかで、分岐させないといけない
// raysのindex 2,3をrectに含んでいれば命中しているということになる
world.Manager.Join(
gameComponents.Position,
gameComponents.SpriteRender,
gameComponents.BlockView,
).Visit(ecs.Visit(func(entity ecs.Entity) {
oPos := gameComponents.Position.Get(entity).(*gc.Position)
oSprite := gameComponents.SpriteRender.Get(entity).(*ec.SpriteRender)
spriteWidth := oSprite.SpriteSheet.Sprites[oSprite.SpriteNumber].Width
spriteHeight := oSprite.SpriteSheet.Sprites[oSprite.SpriteNumber].Height
if !entity.HasComponent(gameComponents.Player) {
x1 := float64(oPos.X - spriteWidth/2)
x2 := float64(oPos.X + spriteWidth/2)
y1 := float64(oPos.Y - spriteHeight/2)
y2 := float64(oPos.Y + spriteHeight/2)
for _, r := range rays {
// x1 == r.X2
if (x1 < r.X2 && x2 < r.X2) || (y1 < r.Y2 && y2 < r.Y2) {
drawRect(shadowImage, blackImage, float32(oPos.X-16), float32(oPos.Y-16), 32, 32, opt)
}
// if x1 < r.X2 || x2 == r.X2 || y1 == r.Y2 || y2 == r.Y2 {
// drawRect(shadowImage, blackImage, float32(oPos.X-16), float32(oPos.Y-16), 32, 32, opt)
// }
}
}
}))
- rayを飛ばして判定しているところで、エンティティにフラグを立てればよさそう
- とすると、エンティティ単位でしか影を切り替えられなくなるな。ちょっと見えていてもテクスチャを全部表示するか、全部視界に入れないと表示しなくなる。微妙
- 近づいた影を消せばよさそう
DONE 旧フィールドまわりを消す
- componentsもいらないものがある
- prefabもいらなくなる
- levelまわりうつすのは、新しいものができてからでよさそう。参考になるからな
DONE state名のフィールドという名前をリネームする
構造体のフィールドとかと同じになるので、あまりよくない命名に思えてきた。dungeonとかだろうか。
DONE カメラ移動を追加する
後からやると大変そうなので今のうちにやっておく。
- 置物はgrid elementにしたほうが楽そうな感じがしてきた。いちいち座標で持っているのが扱いにくい。また別の問題は起きそうだが…
- グリッドの方が生成もしやすいしな
- でもグリッドだと、グリッドごとでしか配置できない。壁とかはそれでいいが、プレイヤーや敵はそうではない。描画で2つ必要になるのは微妙そうな
- カメラはコンポーネント化したほうがいいのだろうか。リソースでよさそうな感じもするが
- 1つしかない想定なのでコンポーネント化はおおげさな感じ
- 頻繁に変わる値だが、リソースだと不適当な感じ
- Playerと同一でよさそうな。いやでも今たまたま同じというだけで、効果によっては変えたいこともありそう。カメラを自分以外に動かしたいこともあるだろ。分けたほうがよさそう
- ズーム率とかも持ちたいから、そこは別にしたい
- cameraはutilsでなくresourcesにあるべきなのでは
DONE ズームを追加する
追加する。
DONE 方向を追加する
追加する。
- 方向による回転描画
- 方向転換
DONE 描画の優先順を指定できるようにする
ワープパッドとキャラでは、キャラを上に表示する。
- キャラクター
- キャラクター同士は重ならない
- ワープパッド
- 同士は重ならない
- フィールドのアイテム
- 同士は重ならない
- これは、SpriteRenderに設定してよさそうか。タイルの種別ごとに設定できれば問題ないよな
- SpriteRenderはengineにあるので、移動させてからいじるか…
- Positionに置くのもよさそう。描画の優先順位なので、単なる描画に使う要素なのだが、まあいいのかな
- depthはあまり種類は多くない。重なる組み合わせは少ない
DONE フィードバックをもらうためにどうするか
いったん最低限で完成させて、フィードバックをもらうのを優先する。
DONE 視界範囲外を描画しない
カメラ外も描画して重くなっている。と考えて描画しないようにしたら、逆に重くなった。キャッシュされているのだろうか。まだ別に必要ではなさそうなので、必要なときにやる。
DONE 階層を追加する
ステージの初期化と、階層移動できるようにする。
- 現状はフロアの概念がない。ステートに入ったときに1フロア分初期化してるだけ
- https://ebitengine.org/ja/examples/isometric.html が参考になりそう
- 階を移動するごとにフロアを生成する
- 今の仕組みだと、オブジェクトが莫大な数になる
- 影描画はしなくてよさそう
- 見えてる部分のオブジェクトとタイルにフラグをもたせて、そこだけ表示すればいい
- 衝突判定が多いのは同じなのでは
- 衝突判定範囲を限れば、そう問題にはならないか
- マップ構造物がオブジェクトだと、マップ生成がしにくい
- 絵の描画と、座標がどのタイルにあたるかがわかればいい。わざわざオブジェクトにしなくてもいい
- 視界の衝突判定はどうするか
- diggerではfield_of_viewという便利関数があった
- 壁はオブジェクトではなく、タイルにする。変わることはないのと、数が多くて大変だから
- タイルも、見えてる部分しか描画しない
- 現状は、見える部分をすべてポリゴンで表示する必要がある。このポリゴンを作るために、周囲を囲ってrayが命中しなければならない
############## ### @ # ###o ## # ##############
DONE タイル位置と座標位置の変換が煩雑
これが全部コンポーネントであれば楽なのだが。まあ、タイルだと衝突判定とかは楽になるから…。
- タイルコンポーネントにするのはありか
- タイルに沿わせたいものと、そうでないものがある
- 壁とかワープホールはタイルに沿わせたい
- 移動体はタイルに沿わせたくない
- 配置方法は共通化したい
- 衝突判定は別でよさそう。タイルに沿わないのは計算が多く必要で、タイルに沿うのは計算が簡単
- resources と GridElement の使い分けがわからない。どちらもタイルを保持しているように見える
- resourcesのほうはタイルの幅と高さか
DONE levelを消す
普通のgameComponent(engineではないほう)でlevelを実装してから、消す感じでいいか。参考になりそうだから消してない。
DONE レイキャスト範囲を狭くする
今後オブジェクトを増やすたびに重くなるので。
ある程度狭い範囲に、範囲を制限する矩形を置くようにした。
- NPCを追加したときに、競合しないか。つまりほかの視界をもつオブジェクトとの兼ね合いをどうするかの問題はある
- それぞれのエンティティごとに競合しない形であれば、よさそうか
DONE 階層設計
ランダムだと難しそうなのでとりあえずは手動か。
どうやって内外を判定すればいいのだろう。
DONE タイルを追加する
- まだタイルがない。壁はタイルにしたい
- タイルで視界判定をするには
- タイルは、正方形に沿うもの。オブジェクトは沿わないもの
- 今はオブジェクトだけで、タイルベースでなく扱いにくい。タイルは大量に生成するものなので
- タイル保持の方法をどうするか。メリットデメリットがあまりよくわからない
- ban-ban-don
- digger
- sokoban
- いったんdigger方式でやるか。生成とかしやすそうだし
- 影はややこしいので、とりあえず忘れる
CLOSE engine コンポーネントを移す
分かれてるのがやりづらい。分け方が利用側からすると不明瞭。ただloaderまわりが大変そう。
- うむむ、共通のものはengine部分にあったほうがよい感じもしてきた
- 依存しないためにinterfaceになるが
- SpriteSheet componentsは移せない。engineのResourceで定義されているから。依存してしまう
- resourceごと移動するか…
CLOSE いったんクリアできるようにする
戦闘抜きで、全体を作る。
- ステージ移動
- ステージは手動で作っておき、それをランダムに選ぶ
- 将来的に複数のステージ生成手法から選べるようにしておく
- クリアイベント
- 20階でクリアできるようにする(仮)
- 戦闘を作成する
- エンカウントする
CLOSE キャラクタを生成する
味方/敵を生成する。
CLOSE 階の生成方法を考える
- ランダム選択の一般階層
- ダンジョンによって選ばれやすさに偏りがある
- 5の倍数の場合は帰還ワープも出す
- すべてのマップに帰還ワープを設定しておく
- ボスの階層
- 特殊マップ
- 固定
DONE 書き換え
gameComponents != nil で検索して、HasComponentで書き換える。
DONE 階層移動できるようにする
- 触れた判定できるようにする
- 触れたときにイベントを発火する
- とりあえずタイル。どのタイルにいるか取得する
- オブジェクトでイベントが発生する場合もある(エンカウント)
https://github.com/kijimaD/ruins/blob/c633001385e79d4ecb783f7fd89ba306730fc3c4/lib/resources/move.go#L70-L74
if oneFrontTile.Contains(TileWarpEscape) {
gameResources := world.Resources.Game.(*Game)
gameResources.StateEvent = StateEventWarpEscape
return
}
field stateの Updateにて。
https://github.com/kijimaD/ruins/blob/c633001385e79d4ecb783f7fd89ba306730fc3c4/lib/states/field.go#L45-L50
gameResources := world.Resources.Game.(*resources.Game)
switch gameResources.StateEvent {
case resources.StateEventWarpEscape:
gameResources.StateEvent = resources.StateEventNone
return states.Transition{Type: states.TransSwitch, NewStates: []states.State{&MainMenuState{}}}
}
- ワープタイルが表示されない
- 座標を正しく指定してなかった。タイル座標を指定する
- Componentに保存するだけだと、1つだけその座標のタイルを取れない。全部ループして探す必要がある
- Tileは複数階層が存在しうるので、タイル座標だけでは一意に定まるわけではない
https://github.com/x-hgg-x/sokoban-go/blob/e9d204aeebe393d730fb4bdcb060d249f1470485/lib/resources/game.go#L157
grid, levelComponentList := utils.Try2(gloader.LoadLevel(gameResources.Package, levelNum, gridLayout.Width, gridLayout.Height, &gameSpriteSheet))
- gridスライスとコンポーネントを返している。gridはResourceに加えられる
- GridElementのままだと、座標で1つだけ取り出して、ということがしづらい。全体にクエリをかけなければいけない。あらかじめスライスで座標順に並んでいれば、一発で取れるのに
- 階層を生成するときに、Resourceに保存しておけばよさそう
#### #..# ....
https://github.com/x-hgg-x/sokoban-go/blob/e9d204aeebe393d730fb4bdcb060d249f1470485/lib/loader/level.go#L315
gameGrid := utils.Try(gutils.NewVec2d(gridHeight, gridWidth, tiles))
- ComponentとResourceの2重管理にならないか
- 2つあっても、初回に生成するだけなら問題ない
- タイル判定必要なのだろうか
- ワープパッドはタイルに沿うので、判定のために必要
- タイルの状態が変わった場合はどうするか。ドアとか、罠とか、スイッチとかはタイルだろうが、状態がある
- 状態によってスプライトを切り替え、複数回起動しないようにする必要がある。このへんはコンポーネントがやりやすい
- Component: 全ループする処理用。描画とか
- Resource: 位置を指定して特定のタイルを処理する用。現在タイルの確認とか
- タイルが情報を持たないのが原因なのでは。entityを入れればよいのでは
階層移動したとき、ワープホールが消えない。
- 既存のワープホールが消えない
- 新しいワープホールに乗っても機能しない
- 描画はされている
- 生成時、座標の縦と横を逆にしていた
DONE 階数をどこに保持するか
- 階層移動したときに、階数までリセットされてしまう
- 便利なのでResourceに保存しておきたい
- いつ階数を初期化するか
- 現状はstateのonstartで実行してるが、これだと移動したときに毎回リセットがかかる
DONE WASMローディング表示がほしい
3秒くらい真っ白画面になるのがよくない。
DONE ランダムに部屋を生成する
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/rect.rs#L4-L9
pub struct Rect {
pub x1: i32,
pub x2: i32,
pub y1: i32,
pub y2: i32,
}
https://github.com/x-hgg-x/sokoban-go/blob/e9d204aeebe393d730fb4bdcb060d249f1470485/lib/utils/vec2d.go#L5-L9
type Vec2d[T any] struct {
NRows int
NCols int
Data []T
}
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map/mod.rs#L14-L26
pub struct Map {
pub tiles: Vec<TileType>,
pub width: i32,
pub height: i32,
pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>,
pub depth: i32,
pub bloodstains: HashSet<usize>,
pub view_blocked: HashSet<usize>,
pub name: String,
pub outdoors: bool,
pub light: Vec<rltk::RGB>,
}
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/mod.rs#L66-L75
pub struct BuilderMap {
pub spawn_list: Vec<(usize, String)>,
pub map: Map,
pub starting_position: Option<Position>,
pub rooms: Option<Vec<Rect>>,
pub corridors: Option<Vec<Vec<usize>>>,
pub history: Vec<Map>,
pub width: i32,
pub height: i32,
}
- マップ生成時はタイルのスライスを保持する。したがってマップ生成は単にスライスをいじくる処理となる
- 生成したタイルマップは、配置するときエンティティ化する
- 例: 壁タイルは BlockPass, SpriteRender, GridElement コンポーネントを持つEntityとなる
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/mod.rs#L89-L93
pub struct BuilderChain {
starter: Option<Box<dyn InitialMapBuilder>>,
builders: Vec<Box<dyn MetaMapBuilder>>,
pub build_data: BuilderMap,
}
- tileをエンティティ化しなくてもいいような気もしてきた
- tileとして、まとめて判定したいのか、〜の性質、で部分で判定したいのかとちらか。タイル判定だとわかりにくいと感じるけどな
DONE 生成を部屋化する
廊下、道に分けて作る。
- 参考実装がどういう流れで使っているかよくわからないな
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/mod.rs#L290
pub fn level_builder(
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map/dungeon.rs#L33-L36
fn transition_to_new_map(ecs: &mut World, new_depth: i32) -> Vec<Map> {
let mut rng = ecs.write_resource::<rltk::RandomNumberGenerator>();
let mut builder = level_builder(new_depth, &mut rng, 80, 50);
builder.build_map(&mut rng);
- rooms とか corridor をどうやってタイルにしているか
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/common.rs#L105
pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) -> Vec<usize> {
- ↑引数のmapのtileを変更しつつ、corridor(tile indexのスライス)を返す
- NewLevel を消して、 builderに置き換えればいいのかな
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/room_draw.rs#L11
impl RoomDrawer {
- 廊下がけっこう大変な件
- 部屋ごとの距離を求めて、最短距離を結ぶ
https://github.com/kijimaD/digger_rs/blob/5804eb99182f3f21b733a7d0d0e34ca610b286c6/src/map_builders/rooms_corridors_lines.rs#L20
fn corridors(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
DONE 廊下を追加する
廊下をつなぐ。
DONE 衝突判定を軽量化する
- 中心距離の近いものだけを接触判定するようにするとよさそう
- フロアを50x50にすると、激しく重くなった
- フロアと接触している壁だけを、影や視線判定の対象にするとよさそう
DONE 脱出ワープホールを配置する
今は次の階層しかない。
DONE 壁の影を軽量化する
先に影画像を生成しておいて、軽くする。
DONE シナリオジャンプを実装する
吉里吉里タグリファレンスを調べた。
- ファイルをまたぐ外部ジャンプ
- ファイル内の内部ジャンプ
- ファイルごとでメモリに読み込まれている雰囲気
- 外部ジャンプはとりあえずいらない
- 外部ジャンプは、ファイルごとで名前空間が切れるとかの理由だろうか。整理されそう
- 関数みたいにパースすればよさそうな
; ラベル定義 *extraflagend ; ジャンプ [jump target=*sel02_loop]
*label1 ... ... *label2 ... ...
- ラベル1
- 文字列
- コマンド
- 文字列
- 文字列
- ラベル2
- 文字列
- コマンド
- コマンド
- 文字列
的な構造となる。
- ラベル
- 文1
- 文2
ラベルテーブルに登録する。
*start 開始 [jmp dest="sample1"] *sample1 終了 *sample2
- 一連のイベントが終了したらどうなるんだ。とりあえず今のように止まるでよい
- jmpしたあとは戻ってこないものとする
- ほかのステートとの連携とか考えると全然わからない。いったん忘れる
- サンプルでは、関数名の登録とかどうやっているか
変数 = fn(){}形式だから、変数と同じ仕組みになっている- evalはオブジェクトを返す。オブジェクトは即値を返すメソッドを持っていて、計算もしくは表示に使える
- 先にlabel名を登録する必要があるような
- jumpEvent{dest: “aaa”}
- いや、別にやらなくてもいいのか。イベント実行時に解釈すればいい
- 投入した時点で、イベントオブジェクトが一意に定まる
- label1: …
- label2: …
- label3: …
- 内部jumpだけでなく、どこから開始する、という命令も使える
- start(“label1”)
- 選択肢が実装できると、メニューも実装できるのか
- 通常のコードは、1つの値を返す。途中のコードはすべて評価される
- ノベルエンジンは、評価はない。途中も必要である
一連の流れ。
p := parse()
e := Eval(p)
events1 := e("sample1")
events2 := e("sample2")
DONE 改行とか直す
- 現状、改行があるたびに別イベントとなっている。なのでクリックが必要である。これを1つにまとめてしまう
- どこまでひとまとめとするか
- 見たままが改行となる
- 自動改行があれば問題ないような
- そんなに手動で改行入れたいことないしな
*start ああああ いいいい[p] かかかか きききき[p]
- [l]を実行した待ちにしたいときに、自動改行が無視されて飛び出す。待ったあとは強制改行でよいだろう
DONE メッセージエンジンのサンプル実装を作る
まだGUIで見られないのでよくわからないため。
DONE メッセージシステムのパッケージを切り出す
今は1パッケージに入っていてわかりにくい。
- テキストを解釈してイベントとし、キューに入れる
- 呼び出し側で解釈してイベントごとに処理していく
- Pop() キューからイベントを取り出す。呼び出し側から実行される。クリック時などを想定
- イベントを通知する。呼び出し側での処理が必要なものもある
- animText 現在表示中としたいテキスト。allとbufを持つ。bufはアニメーション用に1文字ずつ入っていく
- 1文字ずつ送っていくのをどうするか。時間実行だとテストが面倒だからやりたくない。1文字ずつAPIを叩くか、一気に表示するAPIによって文字表示を再現できるようにしたい
- 1文字ずつループにどうやって割り込めばいいのか。キューにskipフラグを持てばよいか
- animTextは状態を持つ、ということになる
- なのでPopしても次のイベントに進まないことがある
- changeBG <呼び出し側での処理が必要>
- clickWait クリック待ち
- sleep <呼び出し側での処理が必要>
- 呼び出し側からエンジンに作用できないといけないのか
- メッセージスキップとか
- Head()
- Popしたときに先頭のやつを実行するのか
- キューを食うのが2つあることが混乱の原因になっている。テキストエンジンと呼び出し側
- queue.Pop()
- 先頭のEventの状態が未完了であればSkip()するだけにする
- queue.Display()
- 勝手に増えていく
- 呼び出し側ではメッセージを表示する。メッセージはどのイベントでも常に表示するものだから
- もう1つキューを追加するのはどうだろう
- 全体バッファと現在バッファがあって、追いつかせるように非同期実行する
- キューの終了もeventとする
- 実行中 -> (強制スキップ) -> 終了 -> 次のタスク
- テキスト送り中にボタン押すと全部文章が表示されるような感じ
- スキップ操作など用のキューも別で用意しておくとよさそう
- Pop()とSkip()を状況に合わせて使い分けたい
- 現在のタスクが終わっていればPop()
- 現在のタスクが終わってなければSkip()
DONE 戻れるようにする
popの逆を辿れるようにしたい。今消しちゃってるのをインデックスにすればよいだろう。
1つ戻るのは、意外に面倒だ。一気に最初に戻るだけにした。
DONE イベント捕捉
今のところテキストだけで、背景変更などを捕捉できていない。
- テキストまわりのインターフェースはbufでやっている。これによって、呼び出し側はそれを画面に表示すればいいだけになっている
- イベントは、チャネルなどに流すのがよさそうか
DONE 『坊っちゃん』でサンプルを作る
作る。
- 任意の場所での改行ができない
「おい」 「おい」 「来たぜ」 「とうとう来た」 「これでようやく安心した」 これは1行ずつ表示したい
改行して、次の行に何もないと、自動改行のカウンタがリセットされてないので横に文字が表示されてしまう。
あいうえお かきくけこ たちつてと<なにぬねの>
この場合、「なにぬねの」を改行してほしい。
DONE シナリオを一覧できるようにする
- 一覧すれば、ジャンプは簡単にできる
CLOSE 視覚影でスプライトが隠れているのを直す
近くのライトで見られるようにしたが、微妙だ。
- 壁の向こう側が見える
- 対策のため1マス分だけ照らすようにしたが、変だ
- やはり、直接視界が当たった部分はフラグをもたせて表示するのがよさそう
- 個別のタイルごとにあるので、新しくコンポーネントを作ったほうがよさそう
IsHideとか
- ここに時間かけてもしょうがないから後回しか
CLOSE raycastを高速化する
- もっとも近い壁を記憶することで、高速化できるという
- https://www.redblobgames.com/articles/visibility/#wall-tracking
DONE 移動時に画像を回転させる
Angleを設定すればよい。
DONE カードを装備できるようにする
防具だけが対象なのを直す。
CLOSE リアルタイムなローグライクがよさそう
- フィールドはRay Casting - Ebitengineという感じ
- タイルごとにターン制で動くという感じでない。細かく移動できる
- 自分が動いたら時間が進行する
- シンボルエンカウントで、回避する方法がある。煙幕的な
- 電力と燃料がある
- 電力は短期的なスタミナ。フィールドでダッシュ、煙幕、掘削で減る。有利に進められるが、時間での制限がある
- 燃料は、腹減り度。電力を使うと早く消費する。なくなるとゲームオーバーになる。移動で減る
DONE 排反コンポーネントを作る
排反を表現する。
Joinのときに指定するのはComponentsで、ecs.NullComponentなどの溜められているデータそのものである。なのでenumにできたりしない。
アイデア。
- エンティティを初期化するときに制約する
world.Manager.Join(
gameComponents.Name,
gameComponents.Pools,
gameComponents.FactionTypeEnemy, // みたいにしたいが、Componentは実際に保持してるデータなのでenumにはできない
)
- Joinで、gameComponentsを直に渡すのではなく、関数をはさむとか
group(aCompo, bCompo)
- myutilsを消す
DONE 戦闘追加する
// デッキ
type Deck struct {
Owner Entity
Cards []Card
}
// 戦闘中の手札
type Hand struct {
Card Card
}
// 戦闘ステート
type BattleState struct {
// 味方の行動値。ある値を超すと敵に行動が移る
// 早く終了すると次のターンに持ち越せる
FriendActVal int
// 敵の行動値
EnemyActVal int
}
type TurnSide int
const (
// 味方のターン
FriendTurn TurnSide iota
// 敵のターン
EnemyTurn TurnSide
)
// 戦闘状態を初期化してステート遷移する
func Trans(BattleState{enemies, level}) {}
// カードを引く。デッキから選択される
func Draw() {}
// 選択カードの効果を適用させる
func Apply(target) {}
// 敵味方のターンを入れ替える
func Toggle() {}
- 手札カードをキャラ各々にすると、よくわからなくなる
- 全体にするとややこしくなる雰囲気がある
- チームの行動ゲージが貯まると、敵が行動する。敵も手札を持っていて、それをランダムに選択する
- 戦闘中に死亡すると、死亡フラグが立ち行動できない
- 戦闘が終了すると敵エンティティによる経験値を得て、敵エンティティを削除する
- 味方チームのターンと敵チームのターンは完全に分かれている
- 持ち物としてのカードと、戦闘中に選択できるカードは明らかに概念が異なる
- キャラごとにデッキと装備を設定できる
- 枚数は固定で、未選択の部分は「通常攻撃」で埋められる
- デッキ設定をするにはどうするか
- Equippedを使うのでいいのかな。装備品と共用すると排除する必要がありビミョーか
- まあ、Wearableと同時に使うから区別はできるか
- Card + Equipped
- Wearable + Equipped
- クリックごとに、文字を出し、状態を遷移させていく
- デッキは空にできない。「基本攻撃」とする
- 味方がそれぞれデッキを持つ方式は、やはりややこしい
- 1人で冒険するのがよい可能性がある
- 1人でデッキ20枚のほうが戦略性とかも考えやすそうな
- ロックマンとかそうだろ
- デッキと複数人はあまり相性がよくないような
- 工数もかかるしな
- キャラは特殊効果をつけるための装置、ともできる。暗視とか
- パーティーのスキルの合算が適用されるなど
- 武器などの戦闘スキル、暗視などのフィールドスキル
- カードとメンバーは直接は紐付かない
- でも敵キャラは、キャラと攻撃が紐付いてほしいよな
- 「軽戦車は、50ミリ砲で攻撃した」
- 味方と敵のカードは共通の仕組みとしたい。実装が楽だし、探索してる感が出る
- 敵のデッキは、生きている敵の種類によって決定する、でよいか。なんだか面倒そうだな
- 石原
- レイガン(2)
- おたけび(1) – 固有
- 体当たり(0)
- 白瀬
- ハンドガン(2)
- 竹刀(2)
- ハンドガン<援護射撃>(1)
- ハンドガン<ワンショットキル>(2)
- 体当たり(0)
- 軽戦車
- 50ミリ砲(2)
- 7.62mm機関銃(2)
- 乱射 – 機関銃(4)
- 体当たり(0)
- 味方が1~4人、敵が1~4人の場合について考えなければならない
- カード風にするのはとりあえず諦めるのもよい
- Cardって名前にしちゃったが
- 普通に装備している手札から選ぶ形式にする。ドローとかはない
- テキストのクリック待ちについては、テキストキューが残っていたらクリックはキュー送り、とすればよい
- 攻撃コマンドの構造体
- 最終的にこれらは分解されてeffect queueに入る
- コマンドのテキスト生成にも使う
- 「石原は50ミリ砲で攻撃」
- 「装甲車に34のダメージ」
- 「装甲車はチェーンガンで攻撃」
- 「石原に14のダメージ」
- 「白瀬に19のダメージ」
command{
// 攻撃する人(死んだらこの攻撃は実行されない}
owner: ecs.Entity
// 攻撃対象
target: ecs.Entity
// ターゲットタイプなどはここから取れる。全体攻撃か、味方対象とか
way: components.Card
}
- systemで、コマンドを処理していく
- ダメージがあるたびにログを出し、クリック待ちにする
- クリックするたびにsystemが実行され、ログが表示される
- Listだと、マウスオーバー時のハンドラが入れられない。前も陥った罠だ
- マウスだと、スクロールが面倒である。どうにかならないか
- マウスオーバー時の色は付けてるから、どうにか
DONE メッセージログ追加
汎用なメッセージを表示できるようにする。メッセージパートの仕組みとは別でよい。
戦闘、フィールドなどのイベントによってメッセージを発行して表示できるようにする。
msglog.BattleLog.Append("イシハラは軽戦車を攻撃した")
msglog.BattleLog.Get(10)
- 残ってたらクリック待ちにしたい
- クリック待ち状態のときは何か表示する
DONE 汎用の選択コンテナを作成する
メニューなど、なにかを一覧して選択するのは多く使うので、作っておきたい。
メニュー。
- 選択肢のリスト
- 現在選択中の番号を示す変数
ゲージ。
- 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)
}
DONE 似たようなUI関数を統合する
オプションをちゃんと使ってないのを直す。
DONE UIをまともにする
凝る必要はないが、あまりにデバッグ感がある。
- verticalContainerを簡単にしたい
- 共通のパラメータをもたせたい
DONE スクロールコンテナをListコンテナに置き換える
わかりやすくなる。
DONE スポーン時は自動で全回復する
いちいち回復させないといけないのが面倒なので。
DONE 戦闘できるようにする
- 敵にもコマンドを選ばせる
- SPが足りない場合は不活性にする
カードの選択テーブル。敵の場合はSPの概念はない。
- 軽戦車
- 50ミリ砲: 0.4
- 7.62mm機関銃: 0.3
- 体当たり: 0.3
- オロチ(複数回攻撃)
- 火炎ブレス: 0.2
- 電撃ブレス: 0.2
- 凍結ブレス: 0.1
- 鉤爪: 0.3
- 咆哮: 0.2
DONE フィールドに敵シンボルを作る
- とりあえず動かないやつ
- ランダムに動くやつ
- 向きによる画像をどうするか
- 自キャラは8方向にしか動かないが、敵キャラはそうではない
- 角度の範囲にすればいいか
- 今は回転だが、回転は見た目があまりよくない
- 画像を5方向分用意して切り替えがよさそう(反転させて全方向にする)
- とりあえず回転はなくしておく
- かぶる問題
- positionを指定したはいいけど、重複検知がやりにくいな
- tileだったら全体管理できていて楽だけど…
- スポーンのとき限定で、スポーン済みタイル配列を持っておいてチェックするとか
- スポーン時はタイルで判断できれば問題ない。スポーン時、すべてのエンティティがタイルに沿った位置にある
- タイルを指定してスポーンするインターフェースにすればよいのでは
DONE 敵や味方を倒せるようにする
- 死んでいる判定をする
- 味方でも敵でもすぐに削除はしない。フラグを立てるだけ
- 味方が全員死ぬとゲームオーバー
- 敵が全員死ぬと勝利
- 戦闘終了時に敵エンティティを削除する
メモ。
- 死亡フラグをどうするか
- わざわざコンポーネントにしなくても、HP0で判定すればよさそう
- 死んだキャラは命令できない。ターゲットにならない
- 「倒した」は出して待ち状態にしたい
- 「敵を全滅させた」
- メッセージ待ちごとにクリック待ちにしたほうが楽でいいのではないか
パーティ。
- 味方の最後に行動するメンバーが死んだときに空のコマンド送りが発生する
- ▼をクリアするクリックと、ターン終わりのクリックがある
- 根本的に扱いにくい
DONE 戦闘勝利時に獲得できるようにする
- 経験値
- 素材
モンスターごとに経験点の倍率を変えればいいだろうか。
- 石原 45 -> 56
- 石原 45 -> 56 <UP>
- レベルアップ時にどういう表示にするか
- levelUp systemを作成する
- 100を超えてた場合レベルを上げて経験点を0にする
DONE VRTを自動コミットさせる
なぜか手元とCIで数バイトレベルの差分が出るようになっている。一度CIでダウンロードしてからコミットしている。面倒なのでコミットまでしてもらう。
DONE 移動を回転方式にする
- w,sでアクセル
- a,dで回転
- このほうが斜め移動が便利であるし、ゲーム性もリアルな感じがする
DONE 敵を移動させる
- 状態遷移させる
- chase
- 視界を実装する
- 敵をどう認識するか
- 一定距離に入るとEntityを認識する
- そのEntityの方向に向かって直進する
- roam
- 壁を認識する
- シンボルのなかでも、移動パターンが違うものがある
- 赤玉A: ランダム移動
- 赤玉B: 規則移動(壁伝い)
- 赤玉C: ランダム移動 + 追跡
// Component
type AIInput struct {
FSM MotionFSM
Vision AIVision
}
type MotionFSM struct {
CurrentState AIMoveState
Vision AIVision
}
// AIInput ================
func Update() {
fsm.Update()
vision.Update()
}
// fsm ================
// fsm でパターン遷移を管理する
// 初期化する
// 最初にroam stateを設定する
NewMover()
// stateを選ぶ。全状態遷移を記載する
mover.ChooseState()
// moverから現在stateをUpdate
mover.Update() // currentState.Update
// 状態を設定する
mover.SetState()
// 移動パターンの実装 ================
// 実際に実行される移動パターン
movepattern.Update()
- 最初から複雑すぎることをやろうとしている。少しづつやろう
- ミニマルな実装が完了した
DONE キーボード操作対応する
する。
DONE CIまわり強化する
linterなども設定する。
DONE 構造化ログを整備する
デバッグしやすくする。
DONE 企画書を書く
曖昧なままやっているので、そろそろやる。
DONE 参考になりそうな実装を調べる
フィールドがパフォーマンスの問題があったり、これからどうすればいいのかがわからない。参考になりそうな実装を探す。
- アニメーションどうやってるんだ
- タイルベースと自由座標移動が混じっているので、わかりづらくなっている
- どちらかでいいのではないか
- 衝突判定が重い
- 画像生成が重い
- 毎回生成しているっぽい
- 視界判定が重い
- たまにワープゲートが到達不可能な場所にある
- とりあえず自動生成はあとでにしたほうがよい
DONE 味方一覧表示を共通化する
いろんなところで使いそうかつ、複数のパーツで構成されているので作成が面倒なので。
DONE ステート切り替えが怪しい部分がある
特にpopしている部分。
- pushで、文字があると重なる
- popしたときにOnStartは走らないので、前の画面を削除するのはダメ
DONE 図形 or 画像描画の方法を考える
UIのために図形描画したい。どうするか。画像を用意すればよいが、いい感じにやるためにはどうすればいいか。
DONE モジュール分けする
名前がかぶってややこしいものは分ける。
- system
- app
- message engine
DONE タイプライター表示する
テキストはアニメーション表示したい。多くの場所で使えるので共通化する。
DONE 敵とエンカウントする
- 敵シンボルと接触すると、シンボルに応じて戦闘を開始する
- 敵を移動させるのが先か
- 基本的に既存のやつを流用できるように見える。移動はタイルベースでないので
- とりあえず、一定ロジックで巡回するやつだけでいい
- 敵によって移動ロジックは変える
- 巡回型
- 追跡型
- characterMotionState は継承元で、その子として各stateがある
- 旋回が必要なやつと必要でないやつがある
- 見た目上滑らかに回転させないと変なものと、変でないものがある
- 2つあると面倒なので回転が必要に統一する
- 画像の回転ってどこでやってるんだ
- Positionのangleフィールドが持っているので描画時に
DONE stateEventとeffectの違いがわかりにくい
- stateEventはそういうのとしてまとめたほうがいい説。今、battle stateでstateEventチェックしている
- systemでstate遷移は相互依存になるのでできない
- stateの共通処理でやってよさそう
DONE メインメニューをきれいにする
- 真ん中寄せする
- 横を広くする
DONE 階層移動をEffectでやる
階層移動をイベントとして、ほかの箇所で使いやすくする。
- 特定のenitityを対象としないeffectだけど、これでいいのかな。参考コードの状況とは異なる。特定のentityではなく、gameResourceを変更する。ただ、アイテムで階層移動とかもしたいので、effectでできればいいか
- 今の階層移動の仕組み、わかりづらいな
DONE イベント部分の設計
1章のうろつきをどうするか考える。
- ローグライト形式にすると物語に関してあまり考えなくてよい
- 繰り返しのゲームプレイに変化をつけやすい
- 設定とかが伝わりにくい可能性がある
- Tipsという形式でオプショナルに読めればよさそう
- Tipsだと自然に紹介できなさそうな感じもする
- あまり物語性はない
- 物語部分は背景やSEつきのメッセージ形式で良い
- 行けるところはランダムで選ばれた4つにする
- 行った回数によってイベントが起こる
- 背反なイベントがある
- 回数を重ねることで仲間になったりアイテムがもらえたりする
- 例
- 市場 x 2 => 整備士が仲間になる
- 広場 x 2 => 回復薬がもらえる
- 単調な感じもする
- イベントによって仲間になったり、アイテムが増えたり、ステータスが変動したりする
DONE アイテム使用・削除をsystem化する
wantsToUseエンティティを生成して、そのエンティティをsystemでキャッチする。
直接削除すると共通処理が追加しにくかったりする。
共通の関数化するだけでよさそうな感じもする。実行順とかがややこしくなるのかな。メッセージを伝える用のエンティティをいちいち作るのが面倒なんだよな。コードも増える。ポリシーを考えなければ。
DONE コメントアウト記法をサポートする
無視する。
DONE 固定敵を指定できるようにする
仕様。
- 20階ごとでボス部屋になる。固定ステージ
- 100階ボスを倒したあとラスボス。倒すとクリア、エンディング
- とくに会話などなく、イベントパッドで戦闘になる
- 倒すと扉が開いてワープパッドの部屋にいける
- 勝つと拠点からショートカットが解禁される
メモ。
- ボス戦闘イベント作成する
- 戦闘内容を注入できるようにする
- テーブルから選ぶ場合
- 固定の敵と戦闘する場合
DONE 設定を導入する
- 開発用と本番用のデフォルト設定を分ける
- 環境変数で個別に変更できる
DONE キーボード説明を追加する
- 使用キーが散逸している
- 開発するうえでの管理ドキュメントになる
DONE 未探索の暗闇を追加する
結局、ミニマップを作成していけばいいかということになった。
未探検の部分は暗くなる。
レイキャストして、タイルごとに探索済みフラグを立てていけばよいだろう。
DONE コミットフックを設定する
- コミット前にフォーマットさせる
DONE WASM版でもセーブできるようにする
ローカルストレージに保存する。
DONE セーブ&ロードできるようにする
セーブする。
DONE バージョン表示する
ビルド時にバージョンを注入しているので、それをメインメニューに表示する。
DONE 仲間入れ替え画面を追加する
入れ替えられるようにする。
- 主人公は固定
- 最大4人
- 各職業で特色があるなかから入れ替える
- 冒険のしやすさと戦闘のしやすさのトレードオフ
- 「探求者イシハラ」みたいにしたいが、そうするとIDが変わるので面倒である。変えにくい
- Name はIDと表示名の両方の役割がある
- 人間系は変わりうる
DONE 接触でイベント発火させる
敵シンボルとのエンカウントとは異なる。
- 接触したときに、テキストをいくつか表示したあとに、クリアにして、メインメニューに戻す
- テキスト表示と、ステート遷移
// ボスとの戦闘
func bossEvent() {
msg(`美しい庭園が広がっている。襲いかかってきた`)
battle('Elder Witch') // 敵グループを選び、戦闘ステートに遷移
msg('宝を入手した') // 戦闘勝利後、戻ってくる
get('黒い珠')
flag(TowerFinish)
// 拠点ステートに遷移
state(HomeState)
}
// フィールドでの汎用イベント
func itemFieldEvent() {
// 後で戻れるように現在ステートを保存しておく
push()
// 本文を表示するステートに遷移する。ラベル名指定で使用できる
addEvent(msgByLabel("アイテム入手汎用"))
// (クリック待ち) クリックされたら
// プリセットのテーブルから選ぶ。取得した結果も表示したいな
getRandomItemByTable("field forest")
// ログを表示するステートに遷移する。文字列を引数に渡す
addEvent(msgByRawText(showEventLog()))
// (クリック待ち)
// 元のステートに戻る
pop()
}
// ================
// 実行ループ
// スタックの先頭を実行する
e := events.Pop()
// イベント遷移があれば遷移
if e.(trans) {
return TransPop{}
}
// 実行。パラメータ変動やアイテム入手など
e()
あるいは。
event := func() {
// テキスト表示
msgByLabel("アイテム入手汎用")
getRandomItemByTable("field forest")
}
State{event}
というようにしたいが、無名関数の中でstateにアクセスしたりができないのではないか。
- フィールドでの戦闘勝利後はフィールドに戻ってほしい
- イベントでの戦闘勝利後は後続のイベントを開始してほしい
- イベント進行ステートを作ればいいかな。会話終了時や戦闘終了時はpopしてもらってイベント進行ステートに戻る。前の続きから開始する
- フラグ管理とかアイテム入手とかあるから、コード形式なのが望ましい
- どうやって戻ってきて再開するか。キューをポップしてから実行してやればいいのかな。実行したものは消えて、戻ると新しいイベントを実行して遷移する、という
- 遷移先がわからないといけない
- 基本、イベントが終わったらpopしてくる、でいいのか
- イベントのスタックを用意する、って感じでよさそう
- 各イベント間の遷移をどう指定するか
- フラグやアイテム追加などのコマンドがあるいっぽうで、メッセージ表示などクリック待ちで次に進めるものもある
- クリックしたあとのアクションをどうするか。場合によって違いそうだが
- 現在のStateは、各ステートで状態遷移を記述している。それを、全体から遷移できるようにする
- 基本的に、メッセージ表示イベントは終了後popすればいい
- 戻る必要のあるものだけ、スタックに保存しておけばいい
- イベント名指定で表示する場合と、直接文字列を指定できるものがある
- 基本的に、終了後ポップするタイプのステートしかない
- テキスト表示
- 戦闘
- 要するにステート遷移系と途中で挟む系ができればいい(アイテム入手など)
- ステートスタック
- イベントステートの中でさらにスタックを保持する
- 共通のステートスタックを使う
- 本質的に、複数のステートをまとめて扱いたいということだ。組み合わせたい
- eventName: "塔の遺跡 最深部イントロ" - eventName: "塔の遺跡 ボス" - eventName: "森の遺跡 最深部イントロ" - eventName: "森の遺跡 ボス"
- とりあえずステータス変化はなしでやってみるか
- 接触でどうやってステートを変化させればいいのか
- 通知用entityを発行する
- steteで通知コンポーネントを検知する
DONE ダンジョンステートが重い
いつのまにかWASM版でFPSが10くらいしか出ないことに気づいた。
DONE 角の壁がないとレイが抜ける
角も埋めるようにする。
DONE フロアの枠をどうするか考える
端が真っ黒なので、わかりづらい。壁で囲うか、色を変えてわかりやすくする。
DONE ステージをシード指定で生成できるようにする
現在はダンジョンステートでのVRTは毎回差分が出るのでオフにしている。
DONE 部屋ビルダーのバリエーションを増やす
大部屋を作る。
DONE 戦闘でのミスを直す
パーティにいないメンバーもコマンドが出るので直す。
DONE デバッグ用のステージデバッグメニュー項目を追加する
すぐ生成できるようにする。項目が多いから、スクロールできないといけないだろうな。
- Columns は未使用なのでは
DONE アイテムを配置する
ステージ初期化時にアイテムを配置する。
DONE フィールドログを表示する
- 文字を表示する
DONE systemのUI関数を移動する
UIを別のpackageに移動する。
DONE ローグライクで必要なくなった箇所を削除する
- 既存の戦闘を削除する
- Positionを削除する
- 仲間を削除する
- InPartyを削除する
- AliveやDeadも削除
- TargetAlivePartyを削除する
- HP/SPバーが消えてしまっている
DONE HPゼロでゲームオーバー画面に遷移する
止まっているので。
keyboardInputはグローバルに保持したほうがよくないか。
DONE ターン世界を導入する
- ターンベースシステムを導入する
- 移動などを対応させる
- ローグライク式に戦闘できるようにする
- action points を TurnBasedにする
- 足踏みできるようにする
メモ。
- キャラクターAP100を仮定せず、実際の値を取得して
- depguardを使ってv1をガードして
DONE パラメータ設計
- HP(体力)
- EP(電気) – 特殊行動。装備によって回復方法が異なる
- SP(スタミナ) – ダッシュなど。空腹度を消費して短時間に回復する
- 空腹度。速度・スタミナペナルティが出る。さまざまな回復アイテムがある
DONE engineの再設計・リファクタリング
- 使いにくい
- 使い分けが不明瞭
- interface{}が多い
- 静的ファイルは一箇所にまとめたいので、embed packageを作ってそれを呼び出すようにしたい
- それをパースするパッケージ
構造。
- assets/assets.go (ファイルembed)
- engine
- resources
DONE セーブデータJSONをソートする
毎回差分が出て何が変わったかわかりにくいのでソートして保存する。
DONE タイプライターを使うように置き換える
- intro
- battle
DONE メッセージページ表示を追加する
- ストーリーメッセージはスキップ可能なメッセージページに表示する(Elona風)
- 専用ウィンドウにメッセージを表示する
- 「閉じる」のアクションを表示する
- 基本的に1ページだけ
- 将来的に取る選択肢を選べるようにする
new := msgBuilder()
new.Message("この中から選べ。").Choice(a, b, c) # aに選択肢文字、適用結果、適用後のメッセージが入る
- typewriterは削除する
- 無駄にややこしいし、ローグライクにそぐわない
- 表示アニメーションはあってもいいが、主要機能のenter待ちはいらない
- SafeSliceの文字列版は消す
DONE 単純なstateを置き換える
メニューやゲームオーバーなど、単純なstateはわざわざ専用のstateではなく、処理だけ渡して共通のステートで処理できるのではないか。
CLOSE 戦闘ステートをテスタブルにする
- テストカバー率が低い
- ステップが多く手動での検証が面倒である
- 複雑である
かなりの時間をかけているが、進んでない。うまく組み込めない。
CLOSE 全体的にテストヘルパーは専用のファイルにまとめる
重複が多いように見える。わかりやすいようにまとめておく。
DONE アイテム等をソートする
まだアイテムは固定の並びになるが、レシピなど一括生成系はmapを経由するため生成順序がずれてVRTが失敗している。
DONE フィールドにアイテムを生成する
- アイテムを生成する
- 拾える
DONE 死亡を状態化する
今はHP0で判定している。しかし、死亡状態は特殊で、回復薬で回復させられなかったり、行動できるかのフラグとして使ったりする。なので、コンポーネント化したほうがよい。
DONE 生成をランダム化する
ある程度ランダム化したい。プレイヤー、モンスター、ワープゲートの出る位置をバラけさせる。
DONE 街マップを作成する
- タイルの種類を増やす
- 置物を増やす(家具など)
- 固定スポーンの仕組みをつくる
DONE タイルをraw管理にする
追加しやすくする。型名で判断させず、プロパティ判断させる。
DONE タイルの種類を増やす
とりあえず土だけ追加した。
ステージ作成が少し面倒になるか。2種類のタイルの違いをファイルに書き出したくないな。勝手に判断して入れてくれるのが一番良い。壁が隣接してたら〜とか。
- 「土」
- 「草」
- 「道」
- 「フローリング」
- 自然タイルのなかにランダムな変化をつける
DONE map作成系をSpecに一本化する
TileSpecのように、EntitySpecでスポーンに必要な情報を一本化する。
tile, propをrawで生成するようにしたい。それを使ってSpecにする。
- 名前
- ブロック
- スプライト名
DONE スプライトシートの読み込みを改善する
現状、追加しにくい。キー指定できるようにした。
DONE rawで未定義フィールドがあった場合はエラーにする
漏れが起こるので。
DONE オートタイルを実装する
素材はあるので、それを使えるようにする。
DONE ロードが壊れている
タイルが表示されない。セーブデータ的に、タイルは保存されているのだが。
DONE ステート遷移がおかしい
- 戻ると真っ黒になる組み合わせがある
- Menuの基本動作がPopなので、前のステートがないとそうなる
- Switch と Pop を混同している
DONE 置物をraw化する
ハードコードされている。
- Name
- Prop
- SpriteRender
DONE 無意味なコンストラクタを削除する
NewDungeonStateWithDepth は指定されたDepthでDungeonStateインスタンスを作成するファクトリー関数
func NewDungeonStateWithDepth(depth int) es.StateFactory[w.World] {
return func() es.State[w.World] {
return &DungeonState{Depth: depth, BuilderType: mapplanner.PlannerTypeRandom}
}
}
// NewDungeonStateWithSeed は指定されたDepthとSeedでDungeonStateインスタンスを作成するファクトリー関数
func NewDungeonStateWithSeed(depth int, seed uint64) es.StateFactory[w.World] {
return func() es.State[w.World] {
return &DungeonState{Depth: depth, Seed: seed, BuilderType: mapplanner.PlannerTypeRandom}
}
}
// NewDungeonStateWithBuilder は指定されたBuilderTypeでDungeonStateインスタンスを作成するファクトリー関数
func NewDungeonStateWithBuilder(depth int, builderType mapplanner.PlannerType) es.StateFactory[w.World] {
return func() es.State[w.World] {
return &DungeonState{Depth: depth, BuilderType: builderType}
}
}
DONE effectsの立ち位置を明確にする
- 現状のattack activityでは直接ダメージ計算して、ログを出している
- いろいろな共通化など考えるとeffectを発行するべきにも思える
- イベントシステムとの使い分けはどうするか
DONE 光源を追加する
光源がある部分は色が変わる。
タイルごとに色のフィルタを設定できればよいのだろうな。
DONE 倒れてから攻撃してくる
死亡判定をターンごとに行っているために、プレイヤーが先に倒していても敵が攻撃してくる。
DONE Stackable Itemにする
Material componentのなかで数量を持っているが、仕様として読み取りにくいのと、ほかにもスタッキングしたいものはあるので別コンポーネントにする。
- 換金用アイテムなどはスタッキングできていい
- 装備など独自ステータスを持つものだけ、スタック不可にする
- フィールド上にある時点では1つのエンティティで、インベントリ追加したタイミングでそのエンティティは削除、すでにあるエンティティの個数に加算する
- 武器などは1つ1つでステータスが違う
- 「電池」も残個数をランダムにしたいが
- 腐敗などアイテムごとで状態が変わるものがある。その場合スタックはどうなるか
- 腐敗システムはなくていいだろう
- そういうアイテムだけstackableを抜く
DONE キャラクターもrawで光源付与する
- プレイヤー
- 敵(火の玉)
- ワープホール
DONE raw.tomlをフォーマットする
書き戻せば並び順も統一できそう。
DONE 売買を導入する
- プレイヤーは所持金を持つ
- 売買できる
DONE stateでエラーハンドリングする
エラーフィールドを作成する。Updateループでerrorがあれば終了させる。
DONE コマンドパターンを導入する
課題。現状、stateに直に入力キーボード分岐していて、テストが難しいのとUIと融合していてわかりにくい。
プレイヤーの操作をすべてactionに変換する。
CDDA のようにactionコマンドパターンを適用してテストしやすくしていきたい。が、現在のstateの実装とは合ってない気もする。どのようにしていけばよいか。
どうやってキーをアクションに変換しているか。
- stateメソッドでアクションを受け取るようにしたほうがいいのではないか。Update(action)
- t.Error() は使わずに極力 assert.Error や require.Errorを使う
- handleInputAsAction など、handle key系のメソッドの名前とシグネチャを揃える
DONE 共通のエラーハンドリング方式を定める
- stateでerrorを変えさせるようにして改善した。ほかは後でいいだろう
- ゲームなのでpanicさせたくない。が、検知はしたい
- 終了させるのは微妙
- デバッグ情報を収集してファイル出力などさせるとよさそう
- テスト可能にするために、スライスで保持しておく。関数を実行したときだけ書き込む
DONE ドアを追加する
- 開閉状態を持つ
- 隣接して移動キーを押すと開閉して進めるようになる
- 開閉状態で視線が通るかが変わる
DONE cardをweaponにする
- リネーム
- 近距離武器は1つだけ装備できるようにする
- 装備している近距離武器を攻撃に使う
- 装備画面でスロットに合わせて候補表示したい
DONE window表示でカーソルが動く
- window表示していると後ろのカーソルも動いてしまう
- 別のサブstateにしたほうがよさそう
- 前はそうではなかったように見える
DONE codecovでカバー率をトラッキングする
- カバレッジ率
DONE 会話可能NPCを配置する
- 接触すると会話ウィンドウを出す
DONE イントロダクションを作成する
- 目的
- 街の説明
DONE entity == 0 で非存在判定している箇所を修正する
ポインタにするべきである。
DONE トリガーでまとめる
- ワープ
- 罠
- NPC
検討。
- アイテムはどうするか
- ドアはどうするか
全部トリガーにしちゃえばよさそうに見える。
- 直上で発揮するか
- アクションで近傍で動作するか
- 近傍でそこに移動しようとすると発生するか
- そうなると攻撃とかもトリガーにならないか
類型。
- ワープ
- 直上でエンター or アクション
- NPC: 移動先とかぶる or アクション
- ドア
- 開ける: 移動先とかぶる or アクション
- 閉じる: アクション
DONE ゲームクリアイベントを追加する
- クリア条件
- 100階のボスを倒し、地上に戻る
- クリアに出るメッセージ
- 地上に戻る
- 眠りについていた人々は目覚めだした
DONE ゲームのキー概念を確定する
参考。
- DarkSoulsのソウル
- Bloodborneの血液
- グレンラガンの螺旋力
- キルラキルの生命繊維(寄生生命)
現実世界の電気に相当する、根源的エネルギー。
- 人々が原理や本質を理解している必要はない(ミスリード)
- 加工できる
- 保存できる
- 遺跡運用に使うエネルギー
由来。
- 地球の古代文明
案。
- 熱
- 蒸気
- マナ
- 光
- 時間
どうやって生成されているか。
- 地球の核で生成される
どうやってエネルギーを得ているか(古代文明)。
- 地下深くに脈状に濃い場所が存在していて、それに沿って収集装置がある
- 階層が空間的に離れたところにあるのはマナを集めるため
- 収集して、加工することで保存している
どうやってエネルギーを得ているか(地上)。
- マナで作られた遺跡の物品を専用炉でくべることで得る
性質。
- 高効率
- 物体を透過する。地中→地上→空中まで浸透する
- 保存・運搬しやすい
- 生命の源になる。マナがない場所では生き物は長く生きられない
遺跡の目的。
- 遺跡がマナを吸収して地上の発展速度を抑える
- 地球のマナを使いすぎると枯渇するので調整する
どうやって保存するか。
- 瓶
- ブロック
- 合金
- 充填容器
どうやって摂取するか。
- 直接取り込めない
- アンプル
- 薬
- 手術
DONE 1000万集めたときのテキストを用意する
エンディングを追加する。
DONE 街NPCに機能をもたせる
いくつかを街でのみ実行できるようにして戦略の制約を与える。
DONE データ定義が重複しているのを直す
test_helper_test は、重複管理しているので消す。
DONE アイテム配置テーブルを実装する
- どういうロジックでアイテムを配置するか
- 固まって配置する
- ふさわしいアイテムを配置する
- 深層になるほど強力・レアになる
具体。
- アイテムの選択
- 階層と価値で選べばよい
- アイテムの置き方
- 偏りをもたせたい。アイテムはまとまって生成される
- 部屋にだけ配置したい
- ステージや空間によって配置アイテムを変えたい
- ステージ「範囲」が決定できれば、それをもとに配置できそう
- CDDAのように「民家」「研究室」「学校」
- 遺跡というより、現実に即した施設のほうが面白そう
- 場所ごとに相対的な率を定義する。洞窟、森、研究所…
- 場所
- 偏在率(近い場所にまとまって生成される率)
- アイテム基本数
- アイテムランダム追加数
- 深度係数
場所ごとはいいアイデアで、敵生成ロジックにも使えそうである。
DONE 敵生成テーブルを実装する
アイテム生成と同様にやる。
DONE urfavecliのバージョンを上げる
v2のままなので新しくする。
DONE 食料アイテムを追加する
どういうテイストにするか。CDDAだと家にありそうな食料で自然だが。
都市。
- 食パン
- ポテトチップス
- おにぎり
植物。
- りんご
- みかん
動物。
- オオカミ肉
DONE パッキング方式を整理する
画像の用意とピクセル対応を手動でやってるが、非効率なので方法を整理する。
DONE 物によってアイテムスプライトを変える
- アイテムに「カテゴリ」を導入する
- 付与コンポーネントで判断するのがよさそう
- カテゴリにスプライトを設定する
- カテゴリだけじゃ足りない感じもしてきた。個別アイテムで指定するのもあり
- たくさんあったほうが楽しいだろう
DONE HUDで日本語を使えるようにする
現状デバッグ用プリントを使っているので、フォントが指定できない。日本語が表示できない。
DONE UIを整理する
- 整合性がない
DONE インタラクティブ可能なオブジェクトはアニメーションさせる
溶け込んでわかりにくい。自動でできないか。
- アニメーションのほうがよさそうか
DONE 2階開始になっている
- ダンジョンは1階から始める
- 街を0階にするかnullにする。
DONE 階数表示のUIをいい感じにする
表示しているがあまり外観がよくない。
DONE todoを解消する
たくさんあるので、対応しておく。
CLOSE enum対策する
いらなそう。
- exhaustive linter導入
- 外部から不正な値を作れないようにする
DONE 一度置くと再度拾ったときに消える問題
- Consumable だけで発生する
- 拾ったときに個数は保持できている
DONE internal下に移動する
未使用関数を検知するため。internal下でないと、ライブラリとして呼ばれる可能性があるので検知できない。
DONE 重さを追加する
所持重量を制約してトレードオフをもたせる。
CLOSE LocationInventoryにownerを追加する
ownerは追加しないことにした。
- 暗黙でPlayerEntityになっているのがわかりにくいし、今後の拡張性に問題になる感じがする。
- ownerにすると明らかにユーザしかやらないような操作….合成や売買…にもすべてownerが必要になる
- そもそも今の時点で、NPCのインベントリを使う予定もあまりない。あるとしても一部なので、そう重要ではない。きれいではあるが
- LocationPlayerInventory, LocationNPCInventory にすることで問題が少なさそう
DONE ItemのLocation移動をヘルパー化する
- 多くの場所で出てくるのでまとめる
- InventoryChanged は移動発生箇所で書くべきか、アクティビティで書くべきか
- 背反なLocation Componentは、別Componentにせず型で変えたほうがよさそうにも見える
- パフォーマンスの悪化を考えたが、Itemで絞れれば、大して多くもないので分岐でもよい
- SpawnではLocation設定せずに分離する
- worldhelper以外で、AddComponentなどを禁止できるか
DONE 関数使用を保証する
- 外部packageから呼ばれてない場合はprivateにする
DONE EquipmentChangedをプレイヤーに付与するように修正する
現状全員分更新していて、効率が悪い。
DONE ホットバーを使えるようにする
アイテム重視なので、切り替えやすくする。
DONE 画像を用意する
用意する。
- タイトル画像
- (480, 360)
- (240, 180)
DONE rawファイルの必須属性をチェックできるようにする
漏れがないようにする。
DONE セーブが壊れてるのを直す
- 遷移が正しくない
- 死んだときにECSをリセットする
- 元/先がメインメニューの遷移は置き換え遷移にしてクリーンにする
CLOSE 画面フィルターを追加してきれいにする
- おすすめの組み合わせ(実装の容易さ順)
もっとも簡単で効果的。
- ビネット効果 - 画面四隅を暗く(DrawImageで半透明画像を重ねるだけ)
- コントラスト調整 - シェーダーで色を調整
- パーティクル効果 - 簡単な点の描画で雰囲気UP
中程度の実装で大きな効果。
- 動的照明 - 既存の視界システムを応用
- スムーズ移動 - 線形補間で移動をアニメーション
- グローエフェクト - 特定スプライトに発光
本格的だが美しい。
- アウトライン - スプライトの輪郭検出と描画
- ブルーム - ガウスぼかしで光を拡散
- カラーグレーディング - 色調の統一感
CLOSE 戦闘システムの再設計 – 1ターン戦闘
以下で考えたが、結局微妙である。とくに、戦闘と地形が乖離するとローグライクの意味がなくなる。
現状ありきたりのもので、特有の面白さがない。
事前に情報をみつつ、どこで交戦するかを選択でき、有利な位置を探れる。
- 遠距離ボーナス: +60%
- 近距離ボーナス(自分のタイル種別): +20%
そのうえで、オプショナルなアイテムを選んで条件を有利にしていく。
選択肢。
- 射撃(距離があるときだけ選択できる、0~1つ選択する)
- ハンドガン
- ライフル
- 近接(1つ選択する)
- 日本刀
- 斧
- 素手
- 補助アイテム(複数選択できる)
- 手榴弾(射撃フェーズで固定ダメージ)
- 煙幕(敵の射撃の命中率が低下する)
戦闘のフェーズ。
- 射撃フェーズ
- 味方と敵が射撃選択肢を持つ場合のみ実行する
- 近接フェーズ
- 必ず発生する
メモ。
- 逃亡は戦闘上の選択肢にない(タイル上で敵と距離をとる)
- 隣接した場合は強制的に戦闘になり、不利な条件になる
- 近隣に複数の敵がいるとき交戦するとチェインして敵が強くなり、被害が増加する
DONE tomlフィールドをソートする
長くなってわかりにくくなってきた。
- ファイルを分割する
- フィールドだけソートする
DONE ゲームループカウントをグローバル化する
そんなに広く使うわけではない。アニメーションカウントのシステムくらいだろうか。なので、グローバルに用意する必要はない。
カウントしてメッセージをアニメーションさせる用。汎用的なのでグローバルでやってよさそう。アニメーションのためのもっとよい方法がある可能性はある。ちゃんと調べないとな…。
DONE アニメーションのやり方を考える
AnimationSystemで実装した。
どうやっているのだろう。
- 最後にアニメーションした時刻を取っておいて、それから経過した時間で決定すればよい
- しかし、アニメーションのたびにそれをあちこちに保存しておきたくない感じはする
DONE プレハブを作成する
- 文字列で小さなタイルを作っておき、それを組み合わせられるようにする
- 「住宅A」「住宅B」「店舗A」「噴水」
タスク。
- ドアスポーンはDoorコンポーネントで判断させる。いったんスポーンしてからRenderSpriteを変更する
- タイル名を小文字にして合わせる
- チャンクのFurnitureフィールドはPropフィールドのほうがよさそう
- パレットの競合対策
- 長さ用の構造体を作る
- テストではLoadを使うようにする
- golden_test が複雑すぎる
DONE 橋を作る
次のステージに移動するための橋を作る。
- ワープホール移動は消す
- 次の階に移動、ハブに帰還も橋である
- 橋は3つから選択する
- 帰還階では橋は4つになる
タスク。
- 橋Aとかがわかりにくい
- templateのロードをどこでやるか問題
- resources にもっておいて初期化時に1度だけ実行する、でもよいがpackage依存が生まれるのが面倒である。テストが大変になる。いまのままにする
- template で 指定するイベントを別コンポーネント or リソースにする。別packageから呼べる形で
- entrance はスポーン地点検索用の位置付きマーカー
- exit は interaction (次の階層に移動)
- sign は interaction (橋の入口でヒント表示する)
- BridgeIDはいらないのでは。座標をキーにすれば取れそう
- 20階など、最終マップに到達したときどうするか
- そのまえに、ローグライクとしてどれくらいリセットをするかも考えていない
- アイテムや装備や金を戻りつつ集める、をゲームの目的として考えていたが、今の階層志向のダンジョンはそういう永続性とマッチしていない感じもする。死んだら多くがリセットされるほうが向いているように見える
- 幅はとりあえず50固定でいいのでは。奥行きは変える可能性がある
DONE 壁を作る
オートタイル壁にする。
- 15番が壁なため、周囲のタイルが接続型スプライトになって、外から見たとき変な感じになる
DONE StateEventがわかりにくい
- interaction -> StateEventだが、使い分けがわかりにくい
- ステート遷移が関係あるものはStateEventではある
DONE StateEventに公開APIを作る
直接変数をやりとりしている。同時にStateEventが指定されないことを保証するために公開APIを使って指定するとわかりやすそう。
CLOSE ステージ情報をResourcesに一元化する
- stateではステート遷移に集中するべきであり、ゲームロジックの詳細を知るべきではない
- フロアをまたいだ情報と、フロア内だけの情報を分ける
- フロア内のリセットAPIを作る
- resoucesがどこからでも書き込み/読み込みできてしまっているので、package外からは読みだけにする
CLOSE spawner interfaceを作って共通に使えるようにする
これらが別々なのも少し気になる(itemとpropはスポーン上同じ扱いができそうにも見える)が、これは必要な引数や必須コンポーネントが違うのでこうなっているはず。
複数のspawnerがあり、それぞれ別の関数を持っている。共通のインターフェースを用意して使いやすくする。
- item
- prop
- NPC
DONE 戦闘システムの再設計 – 古典的ローグライク
環境とのシームレスな体験という点からいって、古典的ローグライクが安定である。ただ、そのままだとほかのローグライクと違いを見いだせないという問題がある。自分なりの工夫で先人を超えたい。
- rimworldを参考にした戦闘
- 周囲のタイルでカバーを設定する
- カバー物を建築できるようにする
- ディフェンス要素があってもよい
- 敵味方ともに遠距離が有利すぎる問題
- 銃弾に制限をつければ問題ない。いかに銃弾を手に入れるかというゲームにもなる
- 銃弾のデメリットもつける。周りの敵が気づいて襲ってくる
- カバーの効果を上げる: 位置取りが重要になる
- 出会い頭に射線に入って一瞬で死ぬ、みたいなのは理不尽なので避けたい
- rimworldとの違い
- 拠点は作らない
- 永住はしない
- オープンワールドではない
- CDDAとの違い
- 現実世界でのサバイバルではない。民家や店はない
- 敵はゾンビではない。強く、ゾンビほど多くない状況で緊迫感を生みだす。単一の戦略、単純な作業にならないようにする。むしろソウルシリーズのように各敵に特徴があり、雑魚敵でも普通にゲームオーバーになる(しかしいくつかの対処法を見つければ楽に対処できる)
- rimworldを参考にしたリアリティのある物質世界
- 大量のパラメータを用意してそこから算出する
- たとえば素材が燃えやすさと重量を決める。個々のアイテムで燃えやすさが決まっているわけでなく、素材で決まるのがリアリティ
- ステージ設計
- ステージは戦闘やアイテムのありかたと密接に関わってくる。1フロアずつ移動していく形式で考えていたが、本質的に迷路で1つのゴールを探していくのが正しいかわからない
- 1フロア切り替え方式だと、簡単に逃げられるのも微妙なところ。脅威をリスクなしで回避できてしまう。また、配置運に大きく左右される
- たとえば比較的細い(50タイル)で、延々と上方向に上がっていきシームレスに生成される、という方式もある。洞窟から森へ、みたいな変化を感じられる。下には戻れず、常に選択になる。そういうローグライクはすでにあった
- シームレスな選択式
- ステージ遷移は3つの「橋」を選んで上に進んで。渡ったら戻れない。つまりプレイヤーの初期位置とゴールは固定である
- 途中迷路になっている箇所もあるものの、基本的に迷うことはない。探索重視ならじっくり進むこともできるし、スルーしたいなら最短で進める。追跡してきた敵もそのまま来る
- 渡るまではフロアは自由に歩き回れる
- 5階ごとに4つの「橋」になる。同様にシームレスに街に戻れる
- シームレスに見えるが、一方通行で内部的には橋上でステージ再作成を行っている
- 定期的に下の橋からボスが発生して、倒す or 一定ターン耐えなければならない。事前に宣告されるので準備する。ローグライクのボス戦は単調になる感じもする。
- それぞれの橋をわたる前に、次のフロアの情報がゲームログに表示して判断の参考にできる
- 「焦げた匂いがする」「機械音がする」「木々のせせらぎが聞こえる」
- アイテムとステージは現実を連想させるものとする
- ステージが「商店街」「工場」「鉱山」でどのようなものがあるか自然に想像できる
- アイテムの数に圧倒されない
- 80%は「昭和5年の日本」で、20%は謎アイテムにする
- 施設系ステージは自動生成が難しそうではある…
DONE ゲームシステムの再設計
現在のシステムは、制約が足りないように見える。単なる劣化コピーである。
ほかのインディーゲームの資料を見ると、いかに自分のアイデアが埋もれているかがわかる。
- 世界観。厳しいほどリスクとリターンが相反して面白い
- ゾンビ
- 放射能
- 深海
- 地下
- 宇宙
- なぜCDDAはおもしろいのか
- アイテム収集
- リスクとリターン。ゾンビ化した制約のある世界でアイテムをあさり、クラフトして強いアイテムを手に入れる
- スキルの成長要素
- 現実感のあるさまざまな施設
- すべてがターンで表現される
- サンドボックス要素が強い。後半は作業ゲー感がある(それでも楽しい)
- 主人公は弱くして知性、環境利用で勝つ
- 熱、水、放射線…
- 極限環境が挑戦の必要性を生み出す
- 奥行きを増やして、フロア内で小拠点を構築していく。数日滞在して、移動していく
- 拠点構築系でもよいが、永住とは相性がよくない
- 拠点がないと、強いアイテムを作る自然な感じが出ない
- 最初は生存第一、後半から宇宙人等を倒すことに…
- 1章: 脱出: 街に到達する
- 2章: 戦闘: 拠点をハブにして物資を集め、ボスを倒す
- 「熱」がゲームのコアロジック。放射率、燃焼効率、熱拡散、摂取効率
- 解禁されていく。生活用品 → 産業用品 → SF
- 主人公は設定可能にするか。キャラによって違う体験、戦略を提供できるとよいが、雪山サバイバルではあまり意味がないようにも見える。技師はツールを持ってるとか、単なるショートカット程度だろう。やるなら職業程度じゃなくて種族レベルの違いにすると面白い可能性はある。火や寒さに対する耐性が全く違う、知性の値が違うなど。値は基本的に不変にして、種族選択の意味が出るようにする。また、複数通りの戦略がないといけない
ローグライクのコアな面白さ。これがわかってないといかせない。
- 再生成
- なので探索が前提
- 2D空間で表現できるものである必要がある
- ターンベース
- ターンに集約して、さまざまなものを意味づけして文字べースで表現することができる(読書、戦闘、探索、販売を同じ仕組みで)
- リソース管理
- これらの要素があるから、サンドボックスとは相性がよい
ターン制の構造的ジレンマ。
- 強み
- 時間リソースに様々な要素を集約できる。読書、クラフト、探索、休息
- 慎重な判断と戦略性
- 複雑な状態管理が可能
- 弱み
- 戦闘が単調化しやすい
- ギミックの即応性が低い。罠を見てから避けるなど
- テンポの悪さ
- 悪魔はBloodborneの敵のように理解の範疇にいる存在
- 場所。現実か、ファンタジーか
- 悪魔崇拝の村(悪魔を呼び出す)
- 魔界(悪魔世界に入る)
- 敵は悪魔だけにする
- 撃ち合いをどう表現するか
- 当たる/当たらないで、当たったら即死するのは微妙。ローグライクの仕組みだとそうなる
- 体温(サバイバル・長期)と 悪魔力(戦闘・短期)に分ける
- ゲームデザインとして、ワールドマップを作る
ストーリーベースで考える。
- ストーリーは最低限にする。背景となる物語は決めるが、ゲーム内で明確に描画するとは限らない
- 偶然から人と悪魔が融合して1つになってしまう。悪魔は敵勢力の罠、遭難によって。低温で死にそうななか、死神は力を使って人を生き延びさせる
- 目がさめて二人は、当面生き延びることで協力を決意する
- モードを切り替えて生き延びる。厳しい自然環境を生き延びつつ、敵対する悪魔将軍を倒す
- 主人公の悪魔は憑依している。もとは実体があった
- 自然環境対策は日常的なもので現実的だが、悪魔と戦うための武器や防具はファンタジー
- 例. ジャケットを着込み焚き火で暖をとり、木の棒で作った十字架の槍などで戦う
- 悪魔モードになると代謝が激増し、食料消費が激増する。体温が40度近くまで上がり逆に危険な状態になる。体温がある程度ないと、起動できない。蓄熱によって、強烈な寒さの場所を通りぬける場合に事前にモードを起動しておくのもテクニックになる
- 主人公の設定や種族は可変にする
レベルベースで考える。
- パフォーマンスや実装が難しいのでCDDAやThe Long Darkのようなオープンワールドにはしない。遷移が発生して毎回リセットされるほうが工数がかからない
- ハブ型にして、脱出ではなく各ステージの悪魔ボスの撃破を目的にする
DONE CRT線をいれる
レトロ効果のため。
DONE WASMが動作してない
エラーを直す。
- Chromeでだけ発生していた。ほかのebiten sampleも同様のエラー内容であったから、ゲーム側が原因ではない
- Firefoxだと動作する
DONE 橋部分は再利用する
現在はステージをすべて再作成しているが、橋部分および橋の上にあるエンティティは削除せず再利用でいいのではないか。
- 生成時のちらつきをおさえる
- モンスターも引き継ぐと面白い。追跡されていたら次の階層までついてくる
- 橋上のアイテムとかは消す
- state switch しているから不可避だろうか
- 画面のUpdateを止めて、その間に新しいstateを作って切り替えればよさそうにも見える
DONE 座標が乱立しているので型にまとめる
まとめる。
- Portal
- PropsSpec
DONE 複数ダンジョンを選択できるようにする
- ダンジョンによって壁の種類や、選ばれるステージ、敵の種類が変わる
- ハブから選んで飛ぶ
DONE フィードバックエフェクトを追加する
わかりやすくする。
- ワープしたときのエフェクト
DONE 温度概念を実装する
気温と体温を追加する。
- ステージの位置によって風、温度が異なる。室内は暖かく、屋外の開けた場所は寒い
- どこまでやるかは検討する
- the long dark や CDDA を参考にする
- ダンジョンやフロアによって気温が異なる
- 時間帯によって気温が変わる
- 服装には水濡れ概念がある
- 水に濡れると風によって急速に冷えていく
- 水濡れはレベルがある。少しの濡れは自然に乾燥する
- 焚き火の近くで過ごすことで乾く
- 服装に保温、防風、防水、重さパラメータがあり、低下を防げる
- 現実と同様にトレードオフである
DONE タイルのパラメータを見られるようにする
- マウスホバーで表示できるといいだろうな。
DONE 街ではダンジョン選択ポータルを配置する
ハブとしてダンジョンを選べるようにする。
DONE コードカバレッジの行一覧をデプロイする
率だけだとどこが足りてないのかわからない。
- AIから見やすい形式にしたい
DONE APの処理追加
- まず、よくわからないコードになっている。ゲーム性の根幹なので整理する
- 足りない場合はターンスキップする
- 複数ターンの処理を追加する。ゲージで表示する
DONE resourcesを分割する
現在はなんでも雑に入っている。
- 静的なデータ
- 動的なデータ
で分割してわかりやすくする。
DONE resources manager の初期化をまとめてできるようにする
循環参照のために、以下ができておらず、個別に初期化する必要があってわかりにくい。初期化漏れが起こりうる。
- resources コンストラクタでの初期化
- testhelperでの自動設定
DONE resources の any を消す
- rawMaster が any なので、どうにかする
DONE movement を actions に統合する
あまり分けている意味がない。
DONE 複数のテーブル選択ロジックをどうするか考える
似通った部分がある。
- command table
- drop table
- item table
が、後々機能を追加するのを考えると安易に共通化するべきではないようにも見える。
DONE キャラクター設定できるようにする
ゲーム開始前に設定する。
- 名前
- 職業
DONE 記号用のフォントを入れる
現在のフォントだと記号の多くが使えず不便である。記号が使えるフォントを入れる。
DONE テーブルUIを追加する
テーブルを表示する。
DONE 通貨をそれらしい記号にする
いまはアルファベットで少し読みにくい。
DONE スキルを追加する
スキルを追加する。
DONE 本を追加する
スキルを伸ばす本を追加する。
- 本でしか伸ばせないスキルもある
- 読み通すには長いターンが必要で、量は本によって異なる
- 読み通すまで対応したスキル経験値を得る
- 本を読む速さはスキルによって異なる
- 本によって対応したスキルレベルがあり、プレイヤーのスキルレベルより低い本は読んでも経験値を得にくい。逆に上すぎても難しくて理解できない
- 危険な状態になると読書は中断する
- 読了状態は本に表示する
- 本によってタイトルや概要は異なる
- マシンガンマガジン vol.43
- 陸軍射撃マニュアル
- 高みのサバイバルライフ
- 本なしで上げられるものは手元で練習してから読むか、より易しい本を探すことになる
DONE 自動売却を追加する
脱出したらアイテム類はすべて売却する。
DONE ゲームオーバー時にゲームログがリセットされない
前のが出ている。
DONE ストーリー再設計
古くなっているところがある。
DONE ボスを追加する
ボスを追加する。
- 部屋を追加する
- ボスモンスターを追加する
- ボスに限らず、戦闘ギミックを追加していく必要がある
DONE たまに閉じ込められる位置にスポーンされるのを直す
出られなくなるのを直す。
DONE 遠距離攻撃を追加する
- 遠距離から有利に攻撃できるが、銃弾を消費するのとリロードで時間を消費する
- 敵からの攻撃をどうするか
- 理不尽に一瞬で死ぬ、みたいなのは避けたい
- CDDAでは敵で撃ってくるものは多くない。固定砲台みたいなのだけ撃ってきた。ちゃんと遠くを見てれば避けられる
- 遠くではなかなか命中しない
DONE ダンジョン進捗管理を追加する
3つ踏破するとクリア、を実装する。
DONE 斜め移動しやすくする
Shiftを押しっぱなしで斜め移動しやすくする。
DONE 進行フラグのセーブ
まだセーブに対応してない。
DONE たまにプレイヤーと敵が重なってスポーンされる
重ならないようにする。
DONE 本を読めるようにする
ランダムに拾えるアイテムによって世界観を知っていく。アイテムコレクション要素。
DONE イベントのテーブルをどうするか考える
いくつかの入力によって、たとえばイベントの戦闘がどうなるかは異なる。あるいはアイテムの取得テーブル。
- 味方のレベル
- 階層
を参考にして、以下のような要素が変動する。
- 戦闘モンスター
- レベル
- 種別
- パーティ構成
- 入手アイテム
モンスターを直接指定したい場合もあるだろう。
DONE コンテキスト移動する
同じ方向キーでも、コンテキストによってじっさいの実行イベントは変化する。こうすることで直感的に操作できる。
- 隣接タイルが空 → 移動
- 隣接タイルに敵 → 攻撃
- 隣接タイルにドア → ドア開閉
- 隣接タイルが採掘可能 → 自動採掘
- 隣接タイルが水域 → 水泳開始
bool avatar_action::move(avatar &you, map &m, const tripoint &d) { const tripoint dest_loc = you.pos() + d; // 1. プレイヤー状態確認 if (you.has_effect(effect_stunned)) { // スタン状態での移動制限 return false; } // 2. 敵・NPC検出 creature_tracker &creatures = get_creature_tracker(); if (Creature *critter = creatures.creature_at(dest_loc)) { // 攻撃処理 you.melee_attack(*critter, true); return true; } // 3. 特殊地形処理 if (m.has_flag(ter_furn_flag::TFLAG_MINEABLE, dest_loc)) { // 自動採掘処理 return try_auto_mining(you, m, dest_loc); } // 4. ドア処理 if (m.open_door(you, dest_loc, !m.is_outside(you.pos()))) { // ドア開閉処理 return true; } // 5. 通常移動 return g->walk_move(dest_loc); }
DONE オープニングを表示する
- 名前入力前のゲーム開始時に表示する
DONE ゲームエディタを作成する
プレーンテキストでゲームコンテンツを追加していくのは無理がある。シンプルなゲームエディタ的なものを作る必要がある。
- マップ
- イベント管理
- アイテム類
CLOSE 足踏みできるようにする
足踏みはローグライクにおいて基本的なテクニックである。
すでに実装済みだった。
DONE プレイ体験を考える
いまのところ他人に面白さを伝えられない。何が足りないのだろうか。
要するに脅威と戦略が足りないように見える。戦略の組み合わせの少なさ。
- 数の圧力 — 敵が1体ずつ来るから、殴り合いで十分になる。CDDAでは大量の賢くない敵の物量との戦いが面白かった
- 環境を使う手段 — ドアはあるが、それ以外に地形を活用する手段がほぼない
- 消耗の実感 — 1戦ごとにHP/弾薬/体温が削られて「次の戦闘に耐えられるか」という不安
- 敵の強さのバランスを取る
DONE ゲームバランスを調整する方法を調べる
どうやればいいのか想像もつかない。
- スプレッドシート的に、各項目でソートして見られるようにする
- DPSとかも1項目とする
DONE コンテンツ追加する
ひたすら追加する。使うかは後で考える。
DONE Steamリリース準備
公開できるように準備する。
ストアのアセット。
- Header Capsule ロゴが少なくとも画像の3分の1を占める
- 920px 幅 x 430px 高さ
- Small Scapsule ロゴが画像の大部分を占める
- 462px 幅 x 174px 高さ
- Main Capsule ロゴが少なくとも画像の3分の1を占める
- 1232px 幅 x 706px 高さ
- Vertical Capsule ロゴが少なくとも画像の3分の1を占める
- 748px 幅 x 896px 高さ
- 背景カプセル
- 1438px 幅 x 810px 高さ
ライブラリのアセット。
- ライブラリカプセル
- 600px x 900px
- ライブラリヘッダー
- 920px x 430px
- ライブラリのヒーロー画像
- 3840px x 1240px
- ライブラリのロゴ
- 1280 x 720
DONE 非nilチェックはifを使わせない
Resourcesからポインタフィールドを消して対応した。
- ありえない箇所で非nilの場合は、という感じにするとわかりづらい。発見が遅れる
- Resourceなど前提的なところ + private 関数で nil でないはずのところはerrorを返さずpanicにするなど
- そもそもnilの可能性をゼロにできればいいのだが…
- Resourceは値を渡すのでポインタにするしかない
DONE メッセージログのテストを追加する
- ログメッセージが出るかは非常に重要である
- グローバルだから、テストできてない
DONE 移動を改善する
押しっぱなしのときの移動がいまいちな感じである。
- リピート速度
- 微妙に遅い反応
- チラツキ
DONE 敵のAIを追加する
- 追跡モード追加する
- デバッグ用にステート、索敵範囲、方向を表示する
- 徘徊モードの移動を自然にする
- 徘徊モードの向きが合ってない
CLOSE 視界距離を敵モンスターごとに持たせる
固定にしない。と考えたが別によい。
CLOSE raw.go の変換を消す
OpenAPI生成に切り替えてもどこかでECSのAddComponent()しないといけないから消せない。
現状raw.tomlとECSコンポーネントの変換がある。コンポーネントもOpenAPIに切り替えればバリデーションが不要になるのではないかというアイデア。
DONE スポーン直後に動けないことがある
何もないのに、どの方向にも移動できない。ターンポイントの回復していない可能性がある。
前のフロアの Spatial Index が残っていたようである。
DONE スポーンに偏りをもたせる
- アイテムや敵はまとめて部屋にスポーンする
- 弱い複数匹の群れ、単独の強い敵、みたいなメリハリをつける
- 場所によって、アイテムや敵のテーマが異なる
- たとえば料理場には料理に関するアイテムがある
DONE enumはopenAPI定義でバリデーションする
- kin-openapi などを使えばバリデーションできるように見える。
DONE 近すぎる位置にゲートが配置されないようにする
最低距離を設定する。
DONE 敵が重なることがある
追跡中に敵が重なる。
DONE 街の場合は階数を表示しない
「0F」を出さないようにする。
DONE セーブ&ロードのJSON仕様をOpenAPI化する
rawと同様にする。
DONE Prop操作を追加する
Itemとの違いをもたせる。
prop にロケーションは必要ないのか?
// IsPickable はエンティティが拾得可能かを判定する。
// フィールドアイテムとInteractableを持たないPropが対象となる
func IsPickable(entity ecs.Entity, world w.World) bool {
if entity.HasComponent(world.Components.Item) &&
ほかと合わせてフィールドは参照にして
/** 耐久値。設定すると破壊可能になる */
@minValue(1)
@maxValue(9999)
hp?: int32;
DONE 選択UI改善する
あまりにもひどいので直す。
DONE VRTを強化する
強化する。
- より実際の処理に近く遷移する
DONE フロアをランダムに暗くする
- デフォルトで視界が開けている
- 暗いフロアもある
DONE メニューUIを統一感をもたせる
バラバラで見にくい。また、テキストベースで記号もあまり使えないのでそれも問題である。
- 現状の文字列整形だとスクロールできない
- 1項目でツールチップを表示できるとよい
- 表形式で表示できるコンポーネントなど作れるとよい
- 単色で見にくい
DONE スプラッシュの外観を改善する
微妙なので改善する。
DONE HPゲージを改善する
いい感じに表示する。
スプラッシュ文字に影をいれる。
DONE ロケーションタグはitemに限定しない
ほかのコンポーネントでも利用できるようにする。
- Itemなどのジャンルタグは、関数で判定すればいいのではないか
- まとめた概念は不要
- Weapon や Consumable さえあればよい
DONE ストア用画像の用意
画像を用意する。
DONE デモ用モードを追加する
見せる用に、ブラウザですぐに試せるようにしておく。
DONE カテゴリ用のタグを整理する
- Item カテゴリは必要なのだろうか
DONE ItemがCount数を保持しているのが違和感
Stackableが持っているべきにも見える。
が、複数不可でも1として扱えるのは便利である。
DONE 収納を追加する
収納/取出できるようにする。
- 複数アイテムの拾得メニューを追加する
DONE entity(0) で返すのを禁止する
エラーを返すときに、ID: 0は有効であるにもかかわらず0を返している。
DONE タイル無しは色を変化させない
タイル外は真っ黒になっていて移動はできないが、そこでも視界影の色付けで変わるので変になっている。変化はさせず真っ黒のままにする。
DONE デバッグ用に現在のコンポーネント数を表示できるようにする
リークしてないか、あるいは未使用のものがないか調べる。
DONE アイテム個数変化時に個数整理する
取得時にそのアイテムだけ個数をまとめている。職業選択経由でアイテムが増えたときや、収納したときなどにまとまらない。
- ビスケット 1個
- ビスケット 2個
DONE 部屋にランダムにドアを生成する
部屋の入口はコードで表現できるから配置できるだろう。
DONE 自動開発の方法を整備する
仕事中に時間がかかるタスクを完全自立で稼働させられるようにする。
- スマホでの命令
- トークンが切れた後も自立実行
- 計画だけ見る
DONE map plannerのテストを追加する
動作確認がしづらい。最小限のゴールデンテストを作って、影響範囲がわかるようにするとよさそう。
DONE map plannerのトレースアニメーションを追加する
各フェーズでタイル状態を保存して、どのように生成されているか目で見て判断しやすくする。
CLOSE map plan 生成結果をloggerで出力する
常に出ているので、ログレベルを設定できるようにする。
DONE ゲームデザインの再考
ストアは開設できたが、まだ他人に勧められない。
DONE メンバーを実装する
シミュレーション的要素にする。
DONE AIの共通化する
NPCはすべて共通化する。
- 隊員が2人いるとうまく動かなくなる
- 敵が隊員を攻撃しない。距離で優先を変える
DONE モダンな書き方ツールを導入する
モダンにできるツールがある。
DONE オートセーブを追加する
- 拠点に戻ったときにオートセーブする
- rimworld的に、セーブに名前をつけられるようにする
- オートセーブは日付で名前がつく
DONE ファクトリ関数でエラーを返せるようにする
- 返せずに困っていた。
DONE Action Paramsがわかりにくい
共用しているので、アクションごとにどのパラメータが使われているかわかりにくい。
DONE グローバル設定を保持できるようにする
別ファイルに保存する。
CLOSE 解像度が固定になっている
結局素材が低解像度なので可変にしても意味ないのでやらない。
変更できるようにする。
グローバル設定を保持する方法を整備してから。
DONE gocritic を入れる
if をネストさせない。
DONE ECSエンジンを型安全にする
変える。
DONE 型アサーションを撲滅する
Linterで防ぐ。
DONE 生成AI設定を強化する
skillsにまとめる。
DONE ストアの絵から方向性を決める
- 仲間
- 戦略ゲーム
- スカベンジング
仲間がたくさんいてもいいが、ゲームバランスが難しいだろう。人が少なくなったらすぐ死ぬ、というのもよくない。食料で自然に成約される、というのがよい。
- Elona ではどうしていたか
- メインクエストを選んで目標地点を決める
- クエストに合わせてノードを選び、目標地点に到達する
- 途中地点で移動、ダンジョンに潜る、村や専門店で買い物などする
CLOSE 広域マップ移動を追加する
移動する。
DONE 高速移動できるようにする
大きなマップに対応できるように高速移動を追加する。
- アニメーションをスキップして高速移動する
- 敵が近くにいるか、前に進めなくなると止まる
- とりあえず直線だけでよい。Shiftを押しながら移動すると高速移動にする
- 『ヤンガスと不思議のダンジョン』的なイメージ
DONE シームレスマップを作成する
- チャンク式で、生成していけばいいのだろうか
- 冷気に飲まれたら破棄していく
DONE CIの最適化
遅い。キャッシュをうまく取れていない可能性がある。
DONE 冷気制約を追加する
追加する。
DONE 定義型にまとめる
- int は広すぎて間違いやすいのでロジック型にまとめる
DONE 永続レベルダンジョンを追加する
今までと違って往復できる。
DONE 重さを扱いやすい表現にする
- 重さはミリグラムなどにして整数で持つ
CLOSE 設計ドキュメントをissue化する
自動実行しやすくする。
DONE オーバーワールドを縦3列にする
1列だと選択がないので変える。
CLOSE スライス型でスライス長を指定できる箇所がある
スライスの長さがわかっている場合は型で明示して堅牢にする。
CLOSE エリアを定義する
1フロアのなかにさらにエリアを定義する。
- 入ったときに名前が出る
- 生成アイテムや敵に偏りが出る
まずはマップのなかで複数のエリアを定義・生成できる必要がある。
- 「武器店」
- 「民家」
DONE 分解システムを追加する
配置物を分解してたくさんのアイテムを手に入れる。
複数ターン経過をどう実装するか。
- CDDA や 7DTD などのスカベンジングゲームにありがちな分解によるアイテム入手要素を入れる
- スキルが上がるほど必要なターンコストが減り、入手アイテムが増える
- 分解のための特定アイテムがないと実行できない。アイテムによって分解できるものは異なる
- 分解アイテムのグレードによっても必要なターンコストが減り、入手アイテムが増える
DONE ゲーム方向性を修正する
直す。
DONE 内装生成する
作る。
CLOSE フレーキーな画像goldenを直す
ややこしくなったので、結局数値を下げるだけに。stateは基準のJSON値を取るのがむずかしい。
- ECS状況をゴールデンファイルとする
- 画像は人が見る用にして、一致率検証はやめる
- 種類、方針を明記する
DONE Propをランダム配置する
内装配置で解決した。
- 現状なにも置物を配置してない
- ほとんどの人工物Propは、ランダム配置すると不自然なのでテンプレート限定である
- 部屋によってPropも分ける
DONE ポインタ関係のユーティリティを追加する
個別にこういうのを用意する必要はないように、共通関数を作る。
func PtrSlice[T any](p *[]T) []T {
if p == nil {
return nil
}
return *p
}
// ptr は値のコピーへのポインタを返す。new の式形式は Go 1.26 から有効で &v と等価
func ptr[T any](v T) *T { return new(v) }
組み込み関数ですでにないのだろうか。
// findByKey はスライスからキー関数でマッチする要素を線形検索する
func findByKey[T any](slice *[]T, keyFn func(T) string, target string) (T, bool) {
for _, item := range PtrSlice(slice) {
if keyFn(item) == target {
return item, true
}
}
var zero T
return zero, false
}
CLOSE plannerTypeやdefinitionTypeを型にする
Name() も型を返すようにする。
type StageKind interface {
// Name は種別の識別名。StageKey.Name と一致し、登録表を引くキーになる
Name() string
// BaseTemperature は基本気温。摂氏
BaseTemperature() int
}
DONE contextをリネームする
Goにおいては context は特別な意味を持つので回避する。ルールにも追加する。
DONE キューブを実装する
コアメカニクス。
- 押した先が壁などで押せないときにpanicする
2026/08/01 08:44:30 コマンド実行が失敗した: アクティビティ検証失敗: その方向へは押せません
exit status 1
- 屋内スタートで、キューブが壁際にあると詰む
- スタート時に限らず、詰みやすい
- 倉庫番みたいなかんじで基本押す、が自由自在よりむしろいいのだが、厳しすぎる
- 迷子にならないように大域地図にキューブの位置を表示する
- 「Spaceのアクションメニューから入れる」のゲームログが出過ぎる感
- そもそもアクションメニューのやり方があまり優れている感じがしない
DONE Propのありかたについて考える
- ItemとPropの違いがわかりにくい
- Item Component は消したが、 Prop も同様に消せるのではないか
DONE Activity と Behavior の整理
Activity は BehaviorXXX に改名するべき。
DONE 複数ターンActivityで残りターンを使うのをやめる
使用可能なAPは変動するので、残りターンを最初に計算して引き算する方式だと計算が合わなくなる。
DONE 屋外の配置物を追加する
建物の中以外は延々と何もない。進んでるかもわからないので、木とか草とかを追加する。
DONE メニューが一貫してないのを直す
- 外観がバラバラ
- 操作ボタンがバラバラ
現状のメニューUIはステップが多く扱いにくいので、変更する。短いキー数で到達できるようにする。
- ダンジョン/各メニューからショートカットで直にメニューに移動できる。これらは上部でタブになっていて、切り替えられる
- 調べる(X)
- 置く(D)
- 食べる(e)
- 読む(r)
- 飲む(q)
- 使う(t)
- 投げる(T)
- 置くのは足元だけにする
DONE ブラーは消す
不要になった。
CLOSE ビルドを保存できるようにする
カスタムビルド概念がなくなった。
- 持ち込むものを設定できる
- コストがかかる
CLOSE スキルや行動によってabilityが成長するようにする
スキルだけでなくアビリティもゆっくり成長するようにする。
DONE ダイス表記で性能指定できるようにする
確率の表現力を上げてより奥深い体験を得られるようにする。
共通のダイス表現を使って文字列表記とパースし、どこでも使えるようにしたい。
// Dice は個数抽選。Base 個の Sides 面ダイスの和に Bonus を足す。1d3+1 は {Base:1, Sides:3, Bonus:1}。
// 定数個数は Sides<=0 とし、そのとき値は Bonus になる。5固定は {Sides:0, Bonus:5}。
type Dice struct {
Base int
Sides int
Bonus int
}
DONE 隊員が多いと新しい階層でのスポーンに失敗することがある
panicする。
2026/07/11 20:10:28 コマンド実行が失敗した: 隊員の配置に失敗: (24,0)の隣接に空きタイルがありません
DONE 設計書の連番を消す
頻繁にコンフリクトして面倒である。
DONE 明かりが不可視タイルから漏れる
壁の向こう側にあっても照らされてしまっている。
DONE 国際化表示する
やる。
DONE NPCがポータル上にいるとインタラクションが発生しなくなる
話しかけられなくなる。
DONE tavern menu を削除する
使わないので削除する。
- メニューを消すins Public
- 関係NPCを消す
- NPCスポーンはraw定義からやる
内部での共通化が意外に難しい。
- アイテムと仲間をハンドリングする必要がある
DONE activity整理する
整理する。
DONE ドア施錠罠を消す
使わなくなったので消す。
DONE tsp をすべて int に変える
余計なキャストが面倒なので、 統一する。
DONE 部屋のアイテムスポーンを追加する
中も追加する。
DONE デフォルト言語を英語にする
- WASM
- ビルド版
DONE アイテムメニューでアイコン表示する
アイテムのアイコン表示するとわかりやすいだろう。
DONE 隊員要素の再考
重要な要素として隊員があるが、サバイバルと相性がよくない。
- キューブができたことで、物資の面で問題なくなった
- バランス調整が難しい
- アイテム管理まわりが難しい
- 隊があると選択肢が広がって面白い
DONE 隊員のインベントリどうする問題
現状見られない。見られるようにすると、アイテムメニュー全般で切り替えないといけなくなってややこしくなる。
CLOSE 体温 → 炉 にするのはどうか
- 燃やすとバフがかかる
- 意図的に出力を変えて、トレードオフの比率を変えられる
DONE UIの整理
ぐちゃぐちゃなのを直す。
- HUDが直接描画
- UI package や関数が乱立
DONE 時間帯による光源の調整
調整する。
CLOSE Propに快適度を追加する
ベッドなど快適なもののうえで過ごすと回復する、など。
CLOSE 謎の「重すぎて動けない」が出る
隊員を消したので解決した。
- 重さ的に全然問題ないのに、ゲームログに「重すぎて動けない」が出る。問題なく動けている
- 別キャラのログなのだろうか
DONE ドアの上に敵がいても閉められる
- 敵やアイテムがいると閉じられなくする
DONE 隊員機能を消す
使わなくなった。
DONE Claude に goimports を使わせる
手動でやっているように見える。
DONE 食べ物の腐敗を追加する
- 早く取るインセンティブ
- 永遠に溜めるのを防ぐ
DONE low poly表示化
簡易な3D表示にしてシンプル画像でもインパクトを与えられるようにする。
DONE states/golden_test の整理
テーブル駆動にする。
DONE テストで実際のスポーン関数を使う
手動で component を付与している箇所が多く、実装との乖離が問題になる。
DONE 通信販売を追加する
メインコンテンツを追加する。
CLOSE UIの選択中カーソルをアニメーションさせる
よくゲームにあるもの。アクティブなことがわかりやすくする。
面倒な割にメリットがないので消す。このゲームはアニメーションを最低限使う。
DONE state VRT の言語設定がバラバラ
すべて英語に統一する。
CLOSE テキスト入力の日本語入力対応する
結局入力が必要なところはわずかなので、保留にする。
対応する。少なくともWindows, WASMではできるとよい。
DONE アイテムのスタックルールを決定する
今のところスタック可能であれば何個でもまとまるが、それだとアイテムの量を表現しにくい感じがする。
アイテム個数が細分化した場合にどうするか。その場合はそのままでいいようにも見える。
DONE VRTをアクション操作にする
本番互換の方法で用意できるようにして再現性を上げる。
- 一般のpackageでキーボードの直接指定を禁止するのはどうか
DONE Go1.27にアプデ
更新する。
DONE 座標を扱うstateが壊れている
カメラの角度が変わったことによるもの。
- 射撃
- 観察
DONE 初期装備に本を持たせる
デバッグ用。
DONE 装備メニューのエントリを右寄せする
ほかと一貫してない。
右寄せに置くとアイコンの行頭がずれて違和感なので、ほかと同様に右寄せの位置には重さを表示することとした。
DONE 初期言語設定はPCやブラウザ設定を尊重する
各ビルドで設定する。
DONE 難易度曲線のメカニズムをターン数にする
- 深さによる難易度向上の撤去
- 日数と施設によるリスクとメリットの上昇
DONE 不自然な前線の廃止
離れるとペナルティがないのであまり意味がない。
全域を寒くする。資源の枯渇で強制的に動かされる。
DONE ボスフロアを消す
使わなくなったので消す。
- ボスフロア
- 遺跡攻略フラグ
- 遺跡攻略エンディング
DONE 建物名の「POI」が不自然なので消す
- POIの代わりにそれぞれ具体的な名前をつける
- 名前が変
- 道路の上に生成されることがある
CLOSE チャンク跨ぎの検証する
射撃など、チャンクをまたいで正しくできるか検証する。
DONE ストアページの更新
- 合わなくなった
- タイトルの変更
- ストアページ画像の背景の再生成
- デモ版で無料ということにする
- 無料版のときに買ったら機会損失になるから
シビアで陰鬱なコールド・サバイバル。
- 朝焼の空
- 雪原
- 地面にポツンとあるキューブ
- 遠くに山の稜線が見える
フォントのライセンスが適切か調べる。
DONE 監査タスクのレビュー
見る。
DONE UI由来のフレーキーテストを直す
ebiten UI で競合してたまに失敗するのを直す。
DONE AST lint をまとめる
テストで実行しているが、1箇所にまとめる。
DONE 体温は寒い方向に限定する
寒さのゲームなのだから、タイマーは暑い方はいらないだろう。
DONE シェルスクリプトのlinterを入れる
増えてきたので。タスク、CIで実行する。
DONE 季節が変化したらゲームログに出す
表示する。
- 日の出、日の入りでゲームログを表示する。
DONE 体温の矢印は体温ゲージと同じ高さにする
体力ゲージを指しているように見える。
CLOSE 依存関係の木をわかりやすく明示できないか
package間の関係性をわかりやすくしつつ、ガードレールにしたい。
linterでやっているが、あまりわかりやすくない。
DONE 夕方が短すぎる
外の色味が変わってから、暗くなるまでが早すぎる。色味も暗さも徐々に変わって夕方感を出したい。
DONE 焚き火を追加する
- 強力に温める手段
- 着火する方法
- 燃料を補充する方法
- どの範囲を温めるか
- 通常のローグライク的な燃焼(延焼)との区別をどうするか
DONE メニュー内でのショートカットキーが効かなくなった
フィールド上からしか、効かなくなった。たとえばメニュー内で r を押しても読書タブに飛ばない。
DONE Ebiten のウィンドウタイトルをつける
名前をつけられるはず。
CLOSE HPが自然回復しない
自然回復させたい。
DONE すでに装備してた場合の決定挙動は比較にする
現状。
- すでに装備していたときに決定ボタンは「外す」である
- 装備の比較がしにくい
対策。
- すでに装備した状態だと、別の装備候補に加えて「外す」がメニューの一番に出る
- 別の装備候補にカーソルがあると比較が出てわかりやすい
DONE 食べ物の回復量が低く満腹になりにくい
たくさん食べるのが面倒である。
DONE ダンジョンメニューにクラフトを追加する
- 合成は廃止して、クラフトにする。名前が変わるだけ-
- 合成NPCも廃止する
- クラフトをメニューからできるようにする
DONE 部位異常を追加する
- 戦闘でたまに、部位に対する異常状態として「骨折」とか「切り傷」とかになって、デバフがつく。そのときは治療する必要がある。治療したうえでターン経過することで回復する。症状によってまちまち
- 低体温のデバフとダメージをつける
- 生活しているなかでたまに、栄養が足りなかったり特定の行動をすることで病気になって、部位に対して異常状態になる。肝臓が悪くなるなど
- 通常の攻撃でのやりとりはHPで行う。HPはターン経過でゆっくりとリジェネレーションし、回復アイテム使用で即時回復する。
- HPを直接やりとりするのは、気軽に戦闘するため、ゲームのテンポを維持するため。また、プレイヤーの手間にならない範囲でリアリティと緊張感を生じさせるために怪我や病気を導入する
DONE 温度判定をワールドレベルにする
- 屋外と屋内の判定
- 季節による温度の変化
- エリアによる温度の判定
DONE 新しい日付に入ったら「N日目」を出す
生き延びることが目的だということをわかりやすくする。
以前、ダンジョンに入ったときに名前を出していた機構を流用したい。現在は state の仕組みが変わったせいで出てない。
DONE bench-compare を sticky にする
コメントが長くならないようにする。
が、前のが上書きされるのも微妙だな。隠されればよい。
DONE WASMで最初にクリックしないとアプリケーションにフォーカスが当たらない
自動でフォーカスされるようにする。
DONE 睡眠を追加する
- 疲労パラメータを追加する
- 疲労ペナルティを追加する
- だから、眠らないといけない
- 温度が適切に近くないと入眠できない
- 睡眠中は代謝が下がって消費を抑えつつ、免疫が上がって回復効果が高い
- 寝具によって睡眠効率が変わる
- 起きる時間は指定できない
- 昼寝しすぎると夜眠れなくなる
- Elonaの疲労度を参考にせよ
DONE WASM版のセーブ&ロードを消す
- WASMはデモ版にする
- ビルドタグで、WASM版ではメニューや機構ごと消す
DONE 疲労の共通メカニズム化
charModifiersに合わせる。
DONE tsp から save 構造を消す
不要になった。
DONE リストUI にインデント指定を足す
- 共通UIにレベルを設定して、グルーピングがわかりやすくする
- 見出しがエントリと区別しにくい件もある。インデントを足すとどうなるか
DONE キューブを運転化する
- ディッガーのように乗り込んで運転できるようにする
- 戦闘力はない
- キューブの耐久がゼロになってもゲームオーバー
- キューブのストーリー的説明
- 燃料が尽きて遭難してきた
- 修理して元の空間に戻るのが目的
DONE 外でも火が赤く照らして変
光の照度を設定して、それ以下のときは照らさないようにする。
外で晴れているときは、日光で照度がかなりあり、まったく照らされないはずである。
DONE 右上の地図をマクロ地図に変更する
- 右上のUIに表示する地図を、現在のミクロ地図でなく大域地図にする
- 大域地図にはキューブを表示する
DONE ワールドマップを徐々に明らかにする
現状全範囲が見えている。現在地周辺のチャンクだけ明らかにする。
DONE 火を追加する
- 着火
- 燃焼
- 燃料の追加
関連
- 参照: KDOC 57: sokoban-goを読む。参考にした
Backlinks
- KDOC 64: バトルディッガー編のゲームデザインを考える
- KDOC 90: 『ゲームシナリオ入門』
- KDOC 101: 先にインターフェースを文書化するとやりやすい
- KDOC 137: 簡単にプロセスの使用メモリを確認する
- KDOC 138: Goプロファイラの使い方を見る
- KDOC 159: テクスチャとスプライトの違いを見る
- KDOC 160: 見下ろし型2Dゲームにおけるレイキャティングの例を見る
- KDOC 253: GoでN段階に可変長引数を渡す
- KDOC 323: ノベルゲームバンドルを作る
- KDOC 516: 2025年をまとめる
- KDOC 529: 状態を示すComponentはどう表現するのがよいか