投稿記事

2022年 10月の記事 (4)

satofumi 2022/10/25 20:00

NPC をコーディングして領地運営するゲーム開発(建築まわりの実装)

作っているゲームは、基本的には "Before We Leave" のような街づくりゲームです。
https://twitter.com/BalancingMonkey/status/1556832126489993217?s=20&t=HiYGCuborE2Dv289fWjsOA
大きく違うのは、作業を行うキャラクターの資材運搬から建築指示、ダンジョン探索の全てをプレイヤーが Lua スクリプトでコーディングする必要があるところです。そういうゲームにした理由は私が遊びたかったからです。

今回は、プレイヤーがどうやって建築を行うかについて書いていきます。

建築の流れ

プレイヤーが書くスクリプトには大きく2種類あり、キャラクター操作以外の指示を行う「布告用」と「キャラクター用」です。
建築は、この布告用のスクリプトに「この位置に建築してね」って記述した上でキャラクター用のスクリプトに「建築予定地があれば、要求される資材を運搬して建築する」というスクリプトを書けばオッケーです。

布告スクリプト(edict.lua)へのコーディング例

   -- 鍛冶屋を (-1, 1) のグリッド位置に建築する。
   world:build(Buildings.Blacksmith, Grid(-1, 1))

このスクリプトが実行されると、指定された場所に建築予定地が作られます。


いい感じですね。

建築する人(builder.lua)のスクリプトのコーディング例

そして、建築する人は
・建築予定地がフィールド上にあれば、
・建築に必要な資材を運んで、
・資材が全て揃ったら建築する。
という行動を行えばオッケーです。
実際にコードにすると、こんな感じです。

   while true do
      -- 建築を開始するために不足しているアイテム名を取得する。
      local itemNames = buildOrder:requestedItemNames()
      if #itemNames <= 0 then
         -- 必要になる資材の運び込みが完了したら抜ける。
         break
      end

      -- 必要になる資材が城塞にあると仮定して建築予定地に運搬する。
      -- 本来は必要になる資材を適切に用意しなければならない。
      local itemName = itemNames[1]

      -- 城塞に移動して要求されたアイテムを取る。
      character:move(keep)
      character:get(itemName)

      -- 建築予定地に移動してアイテムを納品する。
      character:move(site)
      character:put()

      -- 疲れ切っていたら休憩する。
      restIfTired(keep)
   end

   -- 建築に必要な資材を運び終えたので建築を開始する。
   repeat
      -- 建築を行う。建築中に疲れ切ったらこの処理は中断される。
      character:work()

      -- 疲れ切っていたら休憩する。
      restIfTired(keep)

      -- 建築予定地にあるタスクカードを取得する。
      local buildCard = world:taskCard(site)

      -- タスクカードがないか建築資材でなければ
      -- 建築が完了したとみなして建築を終了する。
   until not buildCard or buildCard:itemName() ~= Items.BuildingMaterials

急に難しくなった気がしましたか? はい、その通りです。開発者も難しいと思っていて「これをプレイヤーに書かせてもいいの? 大丈夫?」と悩んでいます。
ただ、ここのコードも1行ずつ見ていけば変な処理はありません。とりあえず、このまま開発を続けます。


建築する人が資材を建築予定地に運んでいる様子。

まとめと今後の予定

資材を運搬してクラフトするだけのスクリプトに比べると建築のスクリプトは急に小難しくなったので開発者としても悩んでいますが、とりあえずこのまま開発していきます。
おそらくですが、ゲーム自体の開発と同じくらい Lua スクリプトのコーディング方法のドキュメント作成が大変そうな気がしています。がんばります。

satofumi 2022/10/18 20:00

NPC をコーディングして領地運営するゲーム開発(道の作り方の仕様変更)

今回はタイル間の移動を速くする要素である道についてです。

今までの道の仕様と雰囲気

今までの仕様だと、タイルに建物を追加すると隣りにある建物や道との間に道を自動で作っており、道が多くなってしまっていました。


この見た目には違和感を覚えたので修正します。

修正後の道の仕様

道については「あまり連結しすぎない」「でもプレイヤーが道を追加できる」を実現するために、以下のような仕様にしました。

  • 城塞と敵の城塞との間には最短経路で通路を作り、これを「街道」と呼ぶ。
    • 街道の上には建物を建築できないようにする。
  • キャラクターがある回数だけタイルの間を移動すると、そこに「道」を作る。
    • 「道」が作られるまでの移動回数は、タイルの移動コストが高いほど多くする。

この仕様にすることで、キャラクターが何回も通ると自然に道が作られるし、キャラクターの移動を制御して道を作ることもできます。
道があると移動が2倍くらい早いので、最短経路を求めるときに既存の道があればキャラクターはそこを通りがちなので、新たな道は必要以上に追加されにくいです。

ある目的地まで道を動的に作る Lua スクリプトの例

function createRoads(from, to)
   -- from から to までの最短経路の Grid 配列を受け取る。
   local route = world:route(from, to)

   -- from の位置まで移動する。
   character:move(from)
   
   --- from から to までで、道がない場所を探す。
   for k, to in ipairs(route) do
      -- 道がなければ、道ができるまで往復させる。
      while not world:roadExists(from, to) do
         character:move(to)
         character:move(from)
      end
   end
end


return function()
   local keep = world:keep()
   local kitchen = world:buildings(Buildings.Kitchen)[1]
   
   -- 要塞とキッチンとの間に道を作らせる。
   createRoads(keep:grid(), kitchen:grid())
end

まとめと今後の予定

今回は道の作られ方の仕様を変更しました。
今後も気付いたことは少しずつでも改善していきます。

satofumi 2022/10/11 20:00

NPC をコーディングして領地運営するゲーム開発(9月末の開発状況)

今回は9月末までのスクリーンショットや動画を貼り付けた、いわゆる開発状況の報告記事です。
進捗を簡単にまとめると「機能はだいたい実装した。エフェクトや UI の作り込みはまだ。アイテムや建物のデータ登録はテストで使う一部のみ終わっている」という状況です。

今回は開発状況についての最初の報告なので、全体的に広く浅くの説明をします。

タイトル画面


機能は実装ずみ、設定パネルも実装済みで言語切り替えも動作します。
アイテムやステージ、シナリオを共有できる Workshop 機能は未実装なのと、ちょっとしたエフェクトを追加したいと考えています。

主人公キャラの見た目の変更


ここで変更した見た目が、主人公の見た目として扱われます。
これも機能は実装ずみです。

ステージ選択


機能は実装済みです。ワールドに複数のステージがあって、ステージを選択するとゲームが始まる仕組みです。体験版では「生産」「探索」「防衛」の3つのチュートリアルを学ぶためのワールドを遊べるようにします。
製品版までにどういうワールドを追加するかは検討中です。

ゲーム画面


ステージを選択するとゲーム画面に移り、ちょっとした会話シーンがあってからゲームが開始します。


画面左上の目標を実現するために、エディタでスクリプトを編集したり、API ドキュメントを参照したり、ヘルプを見たり、といったことが行なえます。

そして、Lua スクリプトを記述したら F5 キーを押して世界を再実行し、どんな感じで動作するかを眺めたりデバッグしたりします。

生産の例(実行速度 x4)

探索の例(実行速度 x1)

防衛の例(途中の実行速度 x64)

まとめと今後の予定

今のところ「最低限の機能があってゲームとして遊べなくはない」という状態で、開発全体から見ると折り返し地点の手前くらいの位置にいるつもりです。
引き続き実装していきます。

satofumi 2022/10/04 20:00

NPC をコーディングして領地運営するゲーム開発(タワーディフェンス的な防衛について)

前回の記事で「探索」について説明したので、今回は敵軍が攻めてくる要素である「防衛」について解説します。

防衛について

防衛とは、敵軍が定期的に敵の拠点から攻めてくるのを撃退する要素です。撃退できずに味方の城塞が壊されるとゲームオーバーです。(城塞のダメージは「修理資材」で回復できます)


敵軍を攻撃させるために、弓を持った領民を城塞や砦に配置すると敵軍を攻撃できるようにします。敵が進軍中かは Lua スクリプト経由で検出できるようにするので、その情報を使って領民を普段の仕事から防衛に切り替えるようなプレイを想定しています。

敵が侵攻してきたら防衛を指示するコード

   local militia = Character:new(edict)
   militia:setScript('militia')

   while world:beforeEnemyArmyAttack() > 0 do
      -- 敵が攻めてくるまでの秒数がゼロより大きければ待ち続ける。
      coroutine.yield()
   end
   
   -- キャラクターにメッセージを送信して行動を切り替えさせる。
   militia:setOrder('defense')

こういう敵の侵攻を監視するスクリプトを指令を出すための edict.lua スクリプトに記述しておき、敵が攻めてきたらキャラクターに防衛('defense') のメッセージを送り、防衛用の行動に分岐できるようにします。
それから、敵軍が攻めてくる道の脇に砦を建設して防衛できるようにします。敵軍と砦が隣接すると、敵軍は移動をやめて互いに攻撃し合うようにします。

ゲームにおける防衛の役割

防衛には大きく2つの目的があります。

1つ目は「ゲームプレイを安定させないこと」です。敵は侵攻を繰り返すごとにじわじわと強くしていく予定です。なので、次の侵攻を防ぐためには味方も少しでいいので強化する必要があります。なんというか「ここまで強化すれば絶対大丈夫」みたいな状態になることは避け「そろそろ防衛しづらくなってきたので、なんか強化するかな」と考えてプレイできることを目指しています。

2つ目は、ゲームプレイにバリエーションを持たせたいからです。例えば城塞に弓を持たせた領民を配置するときにも、普段は農耕をしている領民に防衛させるのか、弓の訓練をさせ続けた兵士に防衛させるかなどをプレイヤーに決めてほしいと思っています。

まとめと今後の予定

今回は防衛について解説しました。次回はスキルについて考えていることを記述します。

月別アーカイブ

限定特典から探す

記事を検索