投稿記事

satofumi 2023/01/15 20:00

satofumi 2023/01/03 20:00

NPC をコーディングして領地運営するゲーム開発(エラー処理)

通しプレイしながら機能実装しています。
今回は、プレイ中に起きたエラーの処理について書きます。あと、城塞で敵を防衛するのに使う「矢」を作るまでをプレイしていきます。

goto 使ったことによるエラーの対処

NPC を操作する Lua スクリプトで continue を使う代わりに goto を使ったわけですが、Unity アセットの MoonSharp でエラーが起きてしまいました。

↓エラーになった記述
`
while true do

  ::continue::
  coroutine.yield()

  -- 建築予定地がなければ自宅に移動させる。
  local sites = world:buildings(Buildings.PlannedConstructionSite)
  if #sites <= 0 then
     character:goHome()
     goto continue
  end

調べたり試行錯誤した結果、このように書くことでエラーが起きなくなりました。

↓エラーにならない記述

::continue::
while true do

  coroutine.yield()

多分、goto で local sites = の行が2回評価されることでエラーが起きた気もするのですが、私が Lua に詳しくなくて謎です。
とりあえずエラーにならない記述をサンプルとして掲載しようと思います。
# 異なる変数を渡したことによるエラー時のメッセージ表示
ゲーム側が用意した Lua スクリプトのメソッドを呼び出してエラーになったとき、今までは下記のように「どこでエラーが起きた」かだけ表示されていました。

↓表示されるエラーメッセージ

LuaScripts/character.lua:(84,3-35): attempt to index a nil value
`

このエラーこの場合の意味は「そのメソッドはありません」です。
ただ、この character.lua はプレイヤーは閲覧できないし、どのメソッドからの呼び出しなのかもわからないのでデバッグ不可能な状況でした。
なので、エラーまでの呼出履歴を表示するようにしました。

↓修正したエラーメッセージ
`
LuaScripts/character.lua:(84,3-35): attempt to index a nil value
an argument was passed that is not of type Building
stack traceback:

[clr]: in function 'Character:canWork'
E:\Document\ScriptedLand\_CustomStages\World_01\Test\lord.lua:(13,6-48): in function '<E:\Document\ScriptedLand\_CustomStages\World_01\Test\lord.lua:4>'

こうすることで、character:canWork() の呼び出しないでエラーが起きてて、それはこの canWork() に渡した引数の型が Building でないよ、というのがわかるようになった、はずです。
# 工房で矢を作ってるあたりの動画
で、機能追加しつつ、いろいろ実装して少し賑やかになった様子の動画がこれです。

<player src="2732836" size="100%" />

よいと思います。
# まとめと今後の予定
ちょっとずつですけど機能追加や改修をしています。引き続きがんばります。

satofumi 2022/12/20 20:00

NPC をコーディングして領地運営するゲーム開発(キャラクター初期化時に引数を渡せるようにした)

必須機能は実装し終えたので通しプレイをしながら機能実装してます。
今回は、キャラクターの初期化時に変数を渡せるようにしたのと、パンを生産するための建物を建築したところまでです。

キャラクター初期化時に変数を渡せるようにする

例えば農場が2箇所にあるようなときに、全く同じスクリプトで農民を2人作ると、同じ場所の農場で働いてしまうと思いました。なので、キャラクターを初期化するときに「この設定の人はこっちで働いてね」みたいな処理を実現するために、任意の引数を渡せるようにしました。

建築する人の建築場所を散らせる例(散らせる意味はあまりない)

   -- 引数を渡さないときのキャラクター生成コード
   local builder = Character:new(edict)
   builder:setScript('builder')

   -- 1 という引数を渡したキャラクター生成コード
   local builder2nd = Character:new(edict)
   builder2nd:setScript('builder', 1)

これらの呼び出しでは builder.lua が読み込まれて実行されるわけですが、このスクリプト中で以下のようにすることで、引数を渡さないときには siteIndex が 0, 渡すと 1 になり建築対象をずらすことができます。

return function(args)
   local siteIndex = args or 0
   
   (中略)
   
   -- 作業する建築予定地を決める。
   local site = sites[1 + (siteIndex % #sites)]

今回は1つの数値を引数として渡しただけですが、Lua の table として渡せば任意個数の値を渡せます。いい感じです。

パンを生産するための建物の建築

建築やキャラクター生成を担当する布告スクリプト edict.lua に、小麦粉を作るための畑を生成する「農場」、小麦粉を粉にする「製粉所」、小麦粉と薪からパンを作るための「キッチン」を建築するように指示して建築させたものがこれです。

↓64倍速での録画

いい感じです。
農場で小麦を収穫して製粉所まで運ぶ人や、小麦粉と薪とを集めてキッチンでパンを作る人のスクリプトはこれから作ります。

まとめと今後の予定

遊びながら必要になった機能を実装する開発方法が、それなりに良く機能していていい感じです。引き続きゲームクリアの条件を満たすまでプレイおよび開発していきます。がんばります。

satofumi 2022/12/13 20:00

NPC をコーディングして領地運営するゲーム開発(ビルダーの実装)

NPC をコーディングして領地経営するゲーム、必須機能は実装し終えたので通しプレイをしながら機能実装してます。

ビルダー(建築する人)の追加

前回までで「布告」を担当するスクリプトに「伐採小屋」と「石切場」を建築しろ、というスクリプトを記述して石切場のデータを追加したので、今回はその続きで「ビルダー」を実装していきます。

↓布告を処理するスクリプト(edict.lua)

return function()
   -- 伐採小屋を建築させる
   edict:build(Buildings.LumberjackHut, Grid(1, 3))
   
   -- 石切場を建築させる
   edict:build(Buildings.StonePit, Grid(6, 3))
end

今回は、この布告スクリプト(edict.lua) の edict:build() によって作られる建築予定地に対して、資材を運搬して建築を行うビルダーのスクリプトを記述します。
edict:build() では「ここにこの建物を建てることにする!」という布告を出しただけなので、資材の運搬と建築は住民が行う必要があります。

で、その住民の行動を Lua スクリプトで記述してみたものがこれです。

↓builder.lua

require 'stage_util'

return function()
   -- 城塞に移動して木槌を装備する。
   local keep = world:keep()
   character:move(keep)
   character:get(Items.WoodenHammer)
   character:equip()

   while true do
      ::continue::
      coroutine.yield()

      -- 疲れたら休憩させる。
      restWhenTired(keep)

      -- 建築予定地がなければ自宅に移動させる。
      local sites = world:buildings(Buildings.PlannedConstructionSite)
      if #sites <= 0 then
         character:goHome()
         goto continue
      end

      -- 最初の建築予定地を作業対象にする。
      local site = sites[1]

      -- 建築のオーダーを開始するために不足しているアイテム名を取得して運搬させる。
      local buildOrder = site:orderData(Items.BuildingMaterials)
      local itemNames = buildOrder:requestedItemNames()

      if #itemNames > 0 then
         local itemName = itemNames[1]

         -- 城塞にある資材を利用する。
         if keep:itemCount(itemName) > 0 then
            character:move(keep)
            character:get(itemName)
            character:move(site)
            character:put()
         end
      else
         -- 建築を開始する。
         character:move(site)
         character:work()
      end
   end
end

えっと「これをプレイヤーに書かせていいの? プログラマでない人にはつらくない?」というのは、毎回思ってます。悩ましいです。

そして、この builder.lua スクリプトを住民1人に割り振るように布告スクリプト(edict.lua)を修正すると、こんな感じになります。

↓edict.lua

return function()
   edict:build(Buildings.LumberjackHut, Grid(1, 3))
   edict:build(Buildings.StonePit, Grid(6, 3))

   -- キャラクターを作成して builder.lua を登録する。
   local builder = Character:new(edict)
   builder:setScript('builder')
end

建築する様子

これらのスクリプトで、ビルダーが実際に建築を行う様子の動画はこんな感じです。

↓ビルダーが建築する様子

小さくて見えにくいですが、建築予定地に石材を運んで、それから建築を行う様子がわかります。
また、伐採小屋の建築が終わると、その周囲の森林タイルが「育成林」になって木材が入手できるようにもなるのもわかります。

よいと思います。

まとめと今後の予定

今回プレイしながら何が楽しいのか改めて考えていました。とりあえず「指示通りにキャラクターが動いて仕事するのを眺める楽しさ」があるとは思っています。

また、いったん動作するようになった後に「木材が不足しがちだから木こりの人数を2人にしてみよう」みたいな改善を楽しめるようにもしたくて、これらの楽しみを引き出せる仕組みは今後もプレイしながら考えていきます。

次は、木材や石材を採取させたり、食料生産をさせるスクリプトをこのフィールドに投入してみようと思います。がんばります。

satofumi 2022/12/06 20:00

NPC をコーディングして領地運営するゲーム開発(岩場と石切場の追加)

前回から、ゲームの通しプレイをしながら不足要素を実装することにしました。
そして、建物の建築に必要な「石材」を入手する方法がなかったので追加します。

岩場の地面タイルの追加

追加して描画してみました。山脈のわきにある1のタイルが岩場タイルです。


違和感がすごいと思ったので色合いだけ変更してみました。

まだ違和感が強いです。
多分、草地のタイルに岩場の描画だけ重ね合わせるのが良いのかも? またいつか考えて修正します。

採石所の建物タイルの追加

これもパラメータを設定して画像登録で追加しました。


この建物パネルの建築コストが仮実装なままなのに気付いてしまいましたが、それもまたいつか修正します。

まとめと今後の予定

とりあえず、無事に追加できたのでよかったです。この建物の動作確認はまた改めて行います。

« 1 2 3 4 5 6 7

月別アーカイブ

限定特典から探す

記事を検索