KDOC 59: ECSを使ってRPGを作る

この文書のステータス

  • 作成
    • 2024-11-10 貴島
  • レビュー
    • 2024-11-11 貴島

WIP プロジェクトのステータス

プロジェクトは進行中である。

概要

Entity Component Systemを使って、サンプルゲームを作る。

↑実際のゲーム画面。ウィンドウにフォーカスして方向キーで操作できる。

↑実際のゲーム画面。クリックもしくはエンターでページ送り。

スケジュール

締め切りがなければ、取捨選択できない。

  • 10月リリース

要素一覧

各要素を縦に深掘りして各要素を完成させていくときだ。

  • オークション
  • 購入(稼ぐ動機)
  • 戦闘
  • プレイヤーのビルド
  • プレイヤー生活
    • 睡眠
    • 料理
  • スキル
  • 健康と病気
  • 世界の寒さと体温
  • UI
  • コンテンツ量
    • アイテム
  • 合成ガチャ
  • マップバリエーション
  • ストーリー
  • 難易度曲線
  • クエスト
  • チュートリアル・ヘルプ
  • 実績
  • ストアページ

Tasks (MUST)

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を取得、配置、などで専用関数をまとめて配置してわかりやすくする
func (pa *PickupActivity) collectProp(actor ecs.Entity, world w.World, propEntity ecs.Entity) error {

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 メッセージが飛び出して表示される

文字数が長くて飛び出しているものがある。

  • description
  • メッセージ

検知、あるいは強制改行できないだろうか。

TODO やる気を追加する

  • やる気が上がると能力にバフがかかる
  • キャラクタによって、何を好むかは異なる
  • やる気は下がるが栄養は多い、などのトレードオフが取れる

Archives

DONE メッセージ表示できるようにする

x-hgg-x/sokoban-goを使って小さいサンプルを作る。

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 階数を移動できるようにする

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)

で、そのデータを読み込んでエンティティとコンポーネントを生成する関数を作る。

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 アイテムリストをebitenUIで作る

いい感じに、スクロールできるようにする。

DONE サイドメニューを表示する

性能を表示するサイドパネル。

  • メニューバーが太いのを直す

DONE UIをリロードせずに反映できるようにする

アイテムを使用したときにUIをリロードしているが、スクロール位置が元へ戻ってしまうのでリロードしないようにする。

また、表示ジャンルの切替もあるので、リロードすると保持しなくて困る。

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 装備画面を作る

  • スロットを作成する
    • コードから装備させる
  • 装備画面を作成する
    • スロット表示画面。各キャラごと
  • 選択画面を作成する
    • ここで選択したものが前で選択したスロットに装備される
    • モードをどう表現するか。これを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 描画の優先順を指定できるようにする

ワープパッドとキャラでは、キャラを上に表示する。

  • キャラクター
    • キャラクター同士は重ならない
  • ワープパッド
    • 同士は重ならない
  • フィールドのアイテム
    • 同士は重ならない
  • これは、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 階層移動できるようにする

  • 触れた判定できるようにする
  • 触れたときにイベントを発火する
    • とりあえずタイル。どのタイルにいるか取得する
    • オブジェクトでイベントが発生する場合もある(エンカウント)
if oneFrontTile.Contains(TileWarpEscape) {
	gameResources := world.Resources.Game.(*Game)
	gameResources.StateEvent = StateEventWarpEscape
	return
}

field stateの Updateにて。

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は複数階層が存在しうるので、タイル座標だけでは一意に定まるわけではない
grid, levelComponentList := utils.Try2(gloader.LoadLevel(gameResources.Package, levelNum, gridLayout.Width, gridLayout.Height, &gameSpriteSheet))
  • gridスライスとコンポーネントを返している。gridはResourceに加えられる
  • GridElementのままだと、座標で1つだけ取り出して、ということがしづらい。全体にクエリをかけなければいけない。あらかじめスライスで座標順に並んでいれば、一発で取れるのに
  • 階層を生成するときに、Resourceに保存しておけばよさそう
####
#..#
....
gameGrid := utils.Try(gutils.NewVec2d(gridHeight, gridWidth, tiles))
  • ComponentとResourceの2重管理にならないか
    • 2つあっても、初回に生成するだけなら問題ない
    • タイル判定必要なのだろうか
    • ワープパッドはタイルに沿うので、判定のために必要
    • タイルの状態が変わった場合はどうするか。ドアとか、罠とか、スイッチとかはタイルだろうが、状態がある
    • 状態によってスプライトを切り替え、複数回起動しないようにする必要がある。このへんはコンポーネントがやりやすい
  • Component: 全ループする処理用。描画とか
  • Resource: 位置を指定して特定のタイルを処理する用。現在タイルの確認とか
  • タイルが情報を持たないのが原因なのでは。entityを入れればよいのでは

階層移動したとき、ワープホールが消えない。

  • 既存のワープホールが消えない
  • 新しいワープホールに乗っても機能しない
    • 描画はされている
    • 生成時、座標の縦と横を逆にしていた

DONE 階数をどこに保持するか

  • 階層移動したときに、階数までリセットされてしまう
  • 便利なのでResourceに保存しておきたい
  • いつ階数を初期化するか
    • 現状はstateのonstartで実行してるが、これだと移動したときに毎回リセットがかかる

DONE WASMローディング表示がほしい

3秒くらい真っ白画面になるのがよくない。

DONE ランダムに部屋を生成する

pub struct Rect {
    pub x1: i32,
    pub x2: i32,
    pub y1: i32,
    pub y2: i32,
}
type Vec2d[T any] struct {
	NRows int
	NCols int
	Data  []T
}
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>,
}
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となる
pub struct BuilderChain {
    starter: Option<Box<dyn InitialMapBuilder>>,
    builders: Vec<Box<dyn MetaMapBuilder>>,
    pub build_data: BuilderMap,
}
  • tileをエンティティ化しなくてもいいような気もしてきた
  • tileとして、まとめて判定したいのか、〜の性質、で部分で判定したいのかとちらか。タイル判定だとわかりにくいと感じるけどな

DONE 生成を部屋化する

廊下、道に分けて作る。

  • 参考実装がどういう流れで使っているかよくわからないな
pub fn level_builder(
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 をどうやってタイルにしているか
pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) -> Vec<usize> {
  • ↑引数のmapのtileを変更しつつ、corridor(tile indexのスライス)を返す
  • NewLevel を消して、 builderに置き換えればいいのかな
impl RoomDrawer {
  • 廊下がけっこう大変な件
    • 部屋ごとの距離を求めて、最短距離を結ぶ
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を高速化する

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 スポーン時は自動で全回復する

いちいち回復させないといけないのが面倒なので。

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 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 固定敵を指定できるようにする

仕様。

  • 20階ごとでボス部屋になる。固定ステージ
  • 100階ボスを倒したあとラスボス。倒すとクリア、エンディング
  • とくに会話などなく、イベントパッドで戦闘になる
  • 倒すと扉が開いてワープパッドの部屋にいける
  • 勝つと拠点からショートカットが解禁される

メモ。

  • ボス戦闘イベント作成する
  • 戦闘内容を注入できるようにする
    • テーブルから選ぶ場合
    • 固定の敵と戦闘する場合

DONE 設定を導入する

  • 開発用と本番用のデフォルト設定を分ける
  • 環境変数で個別に変更できる

DONE キーボード説明を追加する

  • 使用キーが散逸している
  • 開発するうえでの管理ドキュメントになる

DONE 未探索の暗闇を追加する

結局、ミニマップを作成していけばいいかということになった。

未探検の部分は暗くなる。

レイキャストして、タイルごとに探索済みフラグを立てていけばよいだろう。

DONE コミットフックを設定する

  • コミット前にフォーマットさせる

DONE WASM版でもセーブできるようにする

ローカルストレージに保存する。

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 デバッグ用のステージデバッグメニュー項目を追加する

すぐ生成できるようにする。項目が多いから、スクロールできないといけないだろうな。

  • Columns は未使用なのでは

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 メッセージページ表示を追加する

  • ストーリーメッセージはスキップ可能なメッセージページに表示する(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 オートタイルを実装する

素材はあるので、それを使えるようにする。

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 会話可能NPCを配置する

  • 接触すると会話ウィンドウを出す

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 セーブが壊れてるのを直す

  • 遷移が正しくない
  • 死んだときにECSをリセットする
  • 元/先がメインメニューの遷移は置き換え遷移にしてクリーンにする

CLOSE 画面フィルターを追加してきれいにする

  • おすすめの組み合わせ(実装の容易さ順)

もっとも簡単で効果的。

  1. ビネット効果 - 画面四隅を暗く(DrawImageで半透明画像を重ねるだけ)
  2. コントラスト調整 - シェーダーで色を調整
  3. パーティクル効果 - 簡単な点の描画で雰囲気UP

中程度の実装で大きな効果。

  1. 動的照明 - 既存の視界システムを応用
  2. スムーズ移動 - 線形補間で移動をアニメーション
  3. グローエフェクト - 特定スプライトに発光

本格的だが美しい。

  1. アウトライン - スプライトの輪郭検出と描画
  2. ブルーム - ガウスぼかしで光を拡散
  3. カラーグレーディング - 色調の統一感

CLOSE 戦闘システムの再設計 – 1ターン戦闘

以下で考えたが、結局微妙である。とくに、戦闘と地形が乖離するとローグライクの意味がなくなる。

現状ありきたりのもので、特有の面白さがない。

事前に情報をみつつ、どこで交戦するかを選択でき、有利な位置を探れる。

  • 遠距離ボーナス: +60%
  • 近距離ボーナス(自分のタイル種別): +20%

そのうえで、オプショナルなアイテムを選んで条件を有利にしていく。

選択肢。

  1. 射撃(距離があるときだけ選択できる、0~1つ選択する)
    • ハンドガン
    • ライフル
  2. 近接(1つ選択する)
    • 日本刀
    • 素手
  3. 補助アイテム(複数選択できる)
    • 手榴弾(射撃フェーズで固定ダメージ)
    • 煙幕(敵の射撃の命中率が低下する)

戦闘のフェーズ。

  1. 射撃フェーズ
    • 味方と敵が射撃選択肢を持つ場合のみ実行する
  2. 近接フェーズ
    • 必ず発生する

メモ。

  • 逃亡は戦闘上の選択肢にない(タイル上で敵と距離をとる)
  • 隣接した場合は強制的に戦闘になり、不利な条件になる
  • 近隣に複数の敵がいるとき交戦するとチェインして敵が強くなり、被害が増加する

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 複数ダンジョンを選択できるようにする

  • ダンジョンによって壁の種類や、選ばれるステージ、敵の種類が変わる
  • ハブから選んで飛ぶ

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 遠距離攻撃を追加する

  • 遠距離から有利に攻撃できるが、銃弾を消費するのとリロードで時間を消費する
  • 敵からの攻撃をどうするか
    • 理不尽に一瞬で死ぬ、みたいなのは避けたい
    • CDDAでは敵で撃ってくるものは多くない。固定砲台みたいなのだけ撃ってきた。ちゃんと遠くを見てれば避けられる
    • 遠くではなかなか命中しない

DONE ダンジョン進捗管理を追加する

3つ踏破するとクリア、を実装する。

DONE 斜め移動しやすくする

Shiftを押しっぱなしで斜め移動しやすくする。

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 街の場合は階数を表示しない

「0F」を出さないようにする。

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 Action Paramsがわかりにくい

共用しているので、アクションごとにどのパラメータが使われているかわかりにくい。

CLOSE 解像度が固定になっている

結局素材が低解像度なので可変にしても意味ないのでやらない。

変更できるようにする。

グローバル設定を保持する方法を整備してから。

DONE gocritic を入れる

if をネストさせない。

DONE 生成AI設定を強化する

skillsにまとめる。

DONE ストアの絵から方向性を決める

  • 仲間
  • 戦略ゲーム
  • スカベンジング

仲間がたくさんいてもいいが、ゲームバランスが難しいだろう。人が少なくなったらすぐ死ぬ、というのもよくない。食料で自然に成約される、というのがよい。

  • Elona ではどうしていたか
  • メインクエストを選んで目標地点を決める
  • クエストに合わせてノードを選び、目標地点に到達する
    • 途中地点で移動、ダンジョンに潜る、村や専門店で買い物などする

DONE 高速移動できるようにする

大きなマップに対応できるように高速移動を追加する。

  • アニメーションをスキップして高速移動する
  • 敵が近くにいるか、前に進めなくなると止まる
  • とりあえず直線だけでよい。Shiftを押しながら移動すると高速移動にする
  • 『ヤンガスと不思議のダンジョン』的なイメージ

DONE シームレスマップを作成する

  • チャンク式で、生成していけばいいのだろうか
  • 冷気に飲まれたら破棄していく

DONE CIの最適化

遅い。キャッシュをうまく取れていない可能性がある。

DONE 定義型にまとめる

  • int は広すぎて間違いやすいのでロジック型にまとめる

DONE 永続レベルダンジョンを追加する

今までと違って往復できる。

DONE 重さを扱いやすい表現にする

  • 重さはミリグラムなどにして整数で持つ

CLOSE 設計ドキュメントをissue化する

自動実行しやすくする。

DONE オーバーワールドを縦3列にする

1列だと選択がないので変える。

CLOSE スライス型でスライス長を指定できる箇所がある

スライスの長さがわかっている場合は型で明示して堅牢にする。

CLOSE エリアを定義する

1フロアのなかにさらにエリアを定義する。

  • 入ったときに名前が出る
  • 生成アイテムや敵に偏りが出る

まずはマップのなかで複数のエリアを定義・生成できる必要がある。

  • 「武器店」
  • 「民家」

DONE 分解システムを追加する

配置物を分解してたくさんのアイテムを手に入れる。

複数ターン経過をどう実装するか。

  • CDDA や 7DTD などのスカベンジングゲームにありがちな分解によるアイテム入手要素を入れる
  • スキルが上がるほど必要なターンコストが減り、入手アイテムが増える
  • 分解のための特定アイテムがないと実行できない。アイテムによって分解できるものは異なる
  • 分解アイテムのグレードによっても必要なターンコストが減り、入手アイテムが増える

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 tavern menu を削除する

使わないので削除する。

  • メニューを消すins Public
  • 関係NPCを消す
  • NPCスポーンはraw定義からやる

内部での共通化が意外に難しい。

  • アイテムと仲間をハンドリングする必要がある

DONE activity整理する

整理する。

DONE ドア施錠罠を消す

使わなくなったので消す。

DONE tsp をすべて int に変える

余計なキャストが面倒なので、 統一する。

DONE アイテムメニューでアイコン表示する

アイテムのアイコン表示するとわかりやすいだろう。

DONE 隊員要素の再考

重要な要素として隊員があるが、サバイバルと相性がよくない。

  • キューブができたことで、物資の面で問題なくなった
  • バランス調整が難しい
  • アイテム管理まわりが難しい
  • 隊があると選択肢が広がって面白い

DONE 隊員のインベントリどうする問題

現状見られない。見られるようにすると、アイテムメニュー全般で切り替えないといけなくなってややこしくなる。

CLOSE 体温 → 炉 にするのはどうか

  • 燃やすとバフがかかる
  • 意図的に出力を変えて、トレードオフの比率を変えられる

DONE UIの整理

ぐちゃぐちゃなのを直す。

  • HUDが直接描画
  • UI package や関数が乱立

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 不自然な前線の廃止

離れるとペナルティがないのであまり意味がない。

全域を寒くする。資源の枯渇で強制的に動かされる。

DONE ボスフロアを消す

使わなくなったので消す。

  • ボスフロア
  • 遺跡攻略フラグ
  • 遺跡攻略エンディング

DONE 建物名の「POI」が不自然なので消す

  • POIの代わりにそれぞれ具体的な名前をつける
  • 名前が変
  • 道路の上に生成されることがある

CLOSE チャンク跨ぎの検証する

射撃など、チャンクをまたいで正しくできるか検証する。

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 睡眠を追加する

  • 疲労パラメータを追加する
  • 疲労ペナルティを追加する
    • だから、眠らないといけない
  • 温度が適切に近くないと入眠できない
  • 睡眠中は代謝が下がって消費を抑えつつ、免疫が上がって回復効果が高い
  • 寝具によって睡眠効率が変わる
  • 起きる時間は指定できない
  • 昼寝しすぎると夜眠れなくなる
  • Elonaの疲労度を参考にせよ

DONE WASM版のセーブ&ロードを消す

  • WASMはデモ版にする
  • ビルドタグで、WASM版ではメニューや機構ごと消す

DONE 疲労の共通メカニズム化

charModifiersに合わせる。

DONE tsp から save 構造を消す

不要になった。

DONE リストUI にインデント指定を足す

  • 共通UIにレベルを設定して、グルーピングがわかりやすくする
  • 見出しがエントリと区別しにくい件もある。インデントを足すとどうなるか

DONE キューブを運転化する

  • ディッガーのように乗り込んで運転できるようにする
  • 戦闘力はない
  • キューブの耐久がゼロになってもゲームオーバー
  • キューブのストーリー的説明
    • 燃料が尽きて遭難してきた
    • 修理して元の空間に戻るのが目的

DONE 外でも火が赤く照らして変

光の照度を設定して、それ以下のときは照らさないようにする。

外で晴れているときは、日光で照度がかなりあり、まったく照らされないはずである。

DONE 右上の地図をマクロ地図に変更する

  • 右上のUIに表示する地図を、現在のミクロ地図でなく大域地図にする
  • 大域地図にはキューブを表示する

DONE ワールドマップを徐々に明らかにする

現状全範囲が見えている。現在地周辺のチャンクだけ明らかにする。

DONE 火を追加する

  • 着火
  • 燃焼
  • 燃料の追加

関連

Backlinks