Insight into Nie:

〝いい感じ〟のエンカウント率計算

この記事ではNieのエンカウント率計算について解説します。

ここで説明する計算方法を用いることによって、平均エンカウント歩数で設定した値に近い歩数でエンカウントしやすくなるようなエンカウント処理ができるようになります。

動機

素朴なエンカウント処理を考えてみます。

この処理は、平均エンカウント歩数(μ)を 30 とし、一歩歩くごとに 1/30 の確率でエンカウントするかの判定を行うものとします。

そこで、前の戦闘から(あるいはマップに入ってから)何歩でエンカウントするかの分布を計算すると、次図のようになります。

素朴なエンカウント率計算の分布

水色の曲線が、歩き始めて何歩目でエンカウントするかの分布です(左軸)。1歩目でエンカウントする確率は 0.033… ですが、30歩目でエンカウントする確率は 約0.012 程度になります。

これは、歩き始めてN歩目でエンカウントする確率が〝第N-1歩目までエンカウントしない確率×1/30〟(29/30**(N-1) * 1/30)という計算になることによるものです。

従って、この素朴なエンカウント処理では、平均エンカウント歩数として 30 を設定しているのに、プレイヤーの体験としては、1歩目で敵にエンカウントする頻度が最も高い、という現象が起こります。

これは「平均30歩で敵にエンカウントしてほしい」という設定に対し、計算上は正しいものの、ほとんどのケースで体感にそぐわないものではないでしょうか。

平均歩数付近で多くエンカウントするようにする

そこでNieでは、エンカウントの頻度が次図のような分布になる計算方法を用意しています。

平均歩数付近で多くエンカウントする分布

黄色の曲線が、一歩ごとのエンカウント率です(左軸)。たとえば第1歩目のエンカウント率は 約0.001 で 0.033… よりかなり小さく、第30歩目のエンカウント率は 約0.083 で 0.033… よりかなり大きな値になります。

これを元に計算すると、何歩目でエンカウントするかの分布は水色の曲線(左軸)の通りになります。平均エンカウント歩数で指定した 30 のところに山ができているのがわかります。これは、プレイヤーの体験として、平均歩数(30)に近いほどエンカウントする頻度が高いことを意味しています。

また、青色の曲線はその歩数までにエンカウントする確率を表しています(右軸)。

Nieでの実装

その1: エンカウント率にシグモイド関数を使う

この手法では、一歩歩くごとのエンカウント率(黄色の曲線)の計算に、シグモイド関数を使います。すると、歩き始めてからN歩目でエンカウントする頻度の分布は、以下のようになります。

エンカウント率にシグモイド関数を使ったエンカウント分布

これは前項で掲載の図と同じものです。

この手法では、歩くごとのエンカウント率を直接計算するため、エンカウントの判定を行うコードがシンプルになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 今何歩目かの歩数
step = @step_count

# 平均エンカウント歩数
mu = map_data.encounter_step

# 黄の曲線の傾斜をいい感じに調整する
i_scale = 5.0 / mu

# step歩目のエンカウント率
rate = Itefu::Utility::Math.sigmoid((step - mu) * i_scale) * i_scale

この値を使って、エンカウントの判定を行うことができます。

1
2
3
if rand < rate
  # 敵にエンカウントした
end

その2: 歩数ごとのエンカウント頻度が釣鐘型の分布になるよう計算する

この手法では、歩数ごとのエンカウント頻度の分布(水色の曲線)を意図した形に近づけるような計算を行います。

前項の一歩歩くごとのエンカウント率(黄色の曲線)を計算するのに比べると、歩数ごとのエンカウント頻度の分布(水色の曲線)やその歩数までにエンカウントする確率(青色の曲線)といった、プレイヤーの体感をイメージしやすい要素を直接調整できます。

Nieでは、水色と青色の曲線をロジスティック分布に近似させ、その結果に修正を加えています。その結果は、次図の通りです。

平均歩数付近で多くエンカウントする分布

Nieでは一歩ごとにエンカウントの判定チェックを行うので、最終的に、黄色の曲線(一歩ごとのエンカウント率)を計算する必要があります。

まず青の曲線(その歩数までにエンカウントする確率)を計算します。これには、シグモイド関数を用います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 今何歩目かの歩数
step = @step_count

# 平均エンカウント歩数
mu = map_data.encounter_step
mu_modified = Itefu::Utility::Math.max(1, mu - 0.5)

# 青の曲線の傾斜を好きな感じに調整する
i_scale = 5.0 / mu

# step-1歩までにエンカウントする確率
prev_dist = Itefu::Utility::Math.sigmoid((step-1 - mu_modified) * i_scale)

# step歩までにエンカウントする確率
dist = Itefu::Utility::Math.sigmoid((step - mu_modified) * i_scale)

次に、第N歩目までにエンカウントする確率と第N-1歩目までにエンカウントする確率の差分を求めることで、歩き始めてN歩でエンカウントする確率を求めます(水色の曲線)。これは、水色の曲線を累積していくと青色の曲線になるので、その逆の計算を行っています。

1
2
# 歩き始めてちょうどstep歩でエンカウントする確率
delta = dist - prev_dist

最後に、水色の曲線から黄色の曲線を導きます。

「動機」の項目で、歩き始めてN歩目でエンカウントする確率(水色の曲線)が〝第N-1歩目までエンカウントしない確率×第N歩目のエンカウント率(黄色の曲線)〟であることを説明しました。

そこから、第N歩目のエンカウント率(つまり一歩ごとのエンカウント率の第N歩目)を逆に計算すると〝歩き始めてN歩目でエンカウントする確率÷第N-1歩目までエンカウントしない確率〟になります。

1
2
3
4
5
6
# step歩目のエンカウント率
# @rate_hereはここまでにエンカウントしない確率(初期値は 1.0 )
rate = delta / @rate_here

# 次回の計算用
@rate_here = @rate_here - delta

おまけで、黄色の山の右裾でエンカウント率が小さくなりすぎないようにします。(※1)

1
2
3
# 歩数が無限大に近づくと確率が微小になりすぎるので
# 1/muを下回らないようにする
rate = Itefu::Utility::Math.max(rate, 1.0 / mu) if step > mu

この値を使って、エンカウントの判定を行うことができます。

1
2
3
if rand < rate
  # 敵にエンカウントした
end

※1. 黄色の曲線は、グラフ上で右裾が直線になっているように、山の右側で 0.033… を下回らないように設定されています。これは釣鐘型の曲線が無限大に近づくほど確率が下がることを回避するためです。計算のしやすさとわかりやすさから、素朴なエンカウント率計算の値と同じ 1/30 を使用しています。

まとめ

以上で、平均エンカウント歩数に近い歩数でエンカウントしやすくなる計算方法を実装できるようになりました。これによってプレイヤーの体験もより〝いい感じ〟になることが期待できます。<終>

※余談ですが、Nieでは、この記事の内容に加え、エンカウント率が異なるマップをまたいだときに歩数を持ち越すにあたって、小さな工夫を行っています。平均エンカウント歩数がより大きなマップに移動するときはそのまま持ち越し、より小さなマップに移動するときは、前後のマップの平均エンカウント歩数の比を持ち越す歩数に掛けて補正します。Nieは大まかに言えば歩数が増えるとエンカウント率が高まる(のに近い)仕様ですが、この補正によって、エンカウントしやすいマップに入ったとき、前のマップの歩数を持ち越しているせいでいきなりエンカウントが高くなりすぎてしまう、というようなことを起きづらくしています。