Dumb Game Lab

ブログ · 開発の裏側

開発日誌 · 裏側

ネットカフェ育成のお客AI、こう作りました

Justin Lee, Dumb Game Lab ·

ネットカフェ育成(PC방 키우기)のゲーム画面。アイソメトリックのPCバンの中で、PCの席ごとにお客が座り、店の前の歩道ではお客が何人も歩いている。右の席の上に「待機3」の札が出ている

タイクーンゲームのお客の行動ロジック(いわゆるお客AI)は、守ることが3つあれば動きます。席は数ではなく「1マスに1人」で押さえる。人が立っているマスは塞がずに、コスト12を足したA*の経路探索で通す。待ち行列は入口の外に1列だけ作る。ネットカフェ育成(PC방 키우기)でこの3つを選んだ理由と、実測の数字、まだ解けていないことをまとめました。

お客は行程を持ってやってきます

お客は出現するとき、行程(itinerary)を1つ引きます。行程の1コマは「ある種類の設備を1回使う」という意味です。種類の名前で書くので、同じ種類の設備が何台あっても、お客はそのうちの1つを選びます。

PCバンの行程は1コマです。席に座って出ていけば終わりなので、行程としてはいちばん単純な縮退ケースです。行程をまったく渡していないゲームには、「どれか設備を1つ使って出る」という1コマの行程が自動でつきます。

行程には安全装置が1つあります。次のコマの設備が店に1つもなければ、そのコマは飛ばします。まだ建てていない設備を待って、お客がその場で固まることはありません。

席は数ではなく、マスです

最初は、設備ごとに定員を数で持たせるほうが楽に見えます。ところが定員を数で持つと、8人が同じ座標に重なって立つことが起きます。数は何人が使うかしか知らず、誰がどこに立つかは知らないからです。

そこで設備ごとに利用マス(interactionCell)を決め、1マスには1人しか入れないようにしました。お客が目的の設備を選んだ瞬間にそのマスを押さえ、ほかのお客はそのマスを取れません。定員をわざわざ書く必要がなくなり、重なりは構造上起きません。

目的の設備を選ぶとき、候補を均等に引くこともしません。均等に引くと、席のすぐ隣の自販機と部屋の反対側の自販機が同じだけ売れてしまい、どこに置いても売上が同じになります。重みをつけて初めて、配置が売上に効きます。

ネットカフェ育成(PC방 키우기)のゲーム画面。PCの机の前の椅子ごとにお客が1人ずつ座り、真ん中にカウンターと鉢植えが見える
PC1台にマス1つ、マス1つにお客1人です。

人が立つマスは塞がずに、コストを足します

経路は4方向グリッドのA*です。立てるマスか、横切れるマスか、2つのマスの間を通れるかを別々に聞き、最後にそのマスを通るコストを聞きます。座っているお客がいるマスは、このコストが12です。

人を壁にしてしまうほうが簡単です。けれどこのゲームの配置チェックは「設備の前のマスも通れる」という前提で、部屋が分断されないことを保証しています。人が壁になった瞬間にその前提が崩れ、狭い部屋ではお客が丸ごと動けなくなって、その日の売上が0になります。

コストにしておけば、迂回路があるときは必ず回り、本当に道がないときだけ割り込んで通ります。割り込む場面は不自然に見えますが、通路を残さなかった配置が出しているサインであって、エンジンのバグではありません。

経路は1回引いて終わりではありません

コスト12を入れれば終わりだと思っていたのに、ほとんど直りませんでした。原因は経路が古くなることでした。出発時に計算した経路の上に、そのあとで人が座ると、コストはあってもお客は古い経路をそのまま歩きます。

そこで、人のマップは毎ティック丸ごと作り直します。差分で管理すると、どこかの経路で消し忘れが出て、幽霊が通路をずっと塞ぎ続けるからです。歩いているお客は次の1マスを毎ティック見直し、塞がっていればその場で経路を引き直します。お客1人あたり0.2秒に1回です。

引き直した経路がやはりそのマスを通るなら、そのまま進みます。止めると、お互いに待ち合って固まってしまうからです。

混み合った配置で40秒回し、「立っている人を踏んだフレーム」を数えました。コストを入れる前は1,701。コストだけ入れて引き直しをしない状態では1,695で、ほぼそのままでした。コストだけ入れて引き直さなければ、ほとんど何も直りません。原因の本体は、経路が古くなることでした。

ネットカフェ育成(PC방 키우기)のゲーム画面。店の前の歩道を4人のお客が歩いてきて、上の店の中ではPCの席にお客が座っている
歩くお客は、次の1マスを毎ティック確かめ直します。

待ち行列は入口の外に1列です

席が空いていないときにお客を店の中に立たせると、行列が通路を塞いで経路探索が絡まります。だから待機はいつも入口の外の1列です。まだ入っていないお客は外の列に並び、すでに入っているお客は列に並ばず、そのコマをあきらめて出ていきます。席が空いているかは、入口の前の庭のマスで聞きます。以前は出入口のマスで聞いていたので、満席だとお客が扉を開けて入ってから引き返していました。

行列も、いくらでも並べるわけではありません。列の定員は、立てるマスの数とゲームが決めた上限(デフォルトは4)の小さいほうです。定員を超えると並ばずに引き返し、列が満杯なのを歩いている途中で知ったお客は、最後尾の横の歩道で引き返します。

大事なのは、受け入れられなかったお客を黙って消さないことです。あきらめるお客はあきらめのモーションをして、ポップアップを出し、ゲームには「どの種類で、どんな理由で逃したか」が集計されます。ボトルネックが画面に見えなければ、プレイヤーに「もう1台置こう」と考えるきっかけがありません。お客が離れていること自体が見えないからです。

ネットカフェ育成(PC방 키우기)のゲーム画面。PCの席にお客が座り、右の席の上に「待機3」の札が出ている。左には売上+2,135ウォンのポップアップが見える
「待機3」の札で、ボトルネックがどこにあるかが画面からすぐ読めます。

行列に並ぶお客は、全員同じ向きを見ます

行列を作ってみると、プレイヤーから報告が来ました(2026年9月19日)。「待つとき、みんな同じ方向を向いて立っていてほしい」。確認すると、向きは歩いているときにしか決まっていませんでした。歩道から上がってきたお客は壁を、最後尾まで歩いたお客は最後尾を、1マス詰めたお客は列の先頭を見ていて、1本の列に3つの向きが混ざっていました。

直し方は単純です。列の位置に着いたとき、列に並んでいるお客全員が先頭のほうを向くようにしました。外の列は壁に沿ってまっすぐ伸びる1本なので、全員が同じ向きになります。止まる場所ごとに、見る向きを決めておく必要があるというのが、この件で得た決まりです。

列の中では体を動かさないようにもしました。体を揺らして焦りを表現してみたところ、「ふわふわ浮いている」「ひとりで震えている」という反応が2回来たので、いまは呼吸のモーションだけを入れています。

PCバンは、座ったあとが売上の8割です

行程の中でPCバンは1コマですが、売上はそこで終わりません。行程の1コマは「1回立ち寄る場所」なので繰り返せず、座っているお客がときどき何かを追加で頼むのは、行程ではなく使用中フックで起きます。この注文が1日の売上の8割です。

だからお客のロジックは、席をどれだけ早く埋めるかだけでなく、座ったお客がどれだけ長く座ってから注文するかとつながっています。頼んだものが届いていなければ、滞在時間が終わっても席を立たせないオプションがあるのも、同じ理由です。

まだ解けていないこと

歩いているお客同士が一瞬重なる場面は、まだ残っています。経路探索が人をコストとして見るのは、席を取って止まった人(使用中のお客と列の内側)だけで、歩いている人はマップに入れません。歩いている人同士が避け合うようにすると、狭い通路で向かい合ったまま回り続ける膠着が起きるからです。どのみち数フレーム後にはそのマスを離れます。

なので、歩くお客同士が重なるフレームは、ルール上は正常として扱います。ちゃんと直すには、よけ合いではなく譲る順番を設計する必要がありますが、それはいまの範囲の外です。

FAQ

タイクーンゲームのお客AIはどう作りますか?

お客ごとに行程を持たせ、目的の設備の利用マスを1人ずつ押さえ、経路はA*で探します。人が立っているマスは塞がずに高いコスト(ここでは12)を足し、席がなければ入口の外の1列に並ばせる構成です。

タイクーンゲームで、NPCがお互いに詰まらないようにするには?

立っている人を壁にせず、コストとして入れ、そのコストを反映して経路を引き直し続けます。コストだけ入れて引き直しをしないと、ほとんど効果がありませんでした。混み合った配置の40秒の実測で、立っている人を踏んだフレームは1,701から1,695に減っただけです。

待ち行列は、店の中と外のどちらに作るのがいいですか?

ネットカフェ育成では、入口の外の1列だけにしています。中に作ると通路を塞ぎ、経路探索が絡まります。すでに入ったお客は列に並ばずにそのコマをあきらめさせ、あきらめた数は集計して画面に見せます。

席が取れずに出ていったお客は、そのまま消してもいいですか?

消してはいけません。あきらめのモーションとポップアップを見せ、種類ごとに集計することで、プレイヤーがボトルネックを読み、「もう1台置こう」と判断できます。

ほかの記事

新作ゲームと小さなお知らせを、月に1回ほどお届けします。

他の言語English한국어日本語繁體中文