はじめに
『Cookie Clicker』1のような放置系ゲームや、『魔界戦記ディスガイア』シリーズ2などで、「100京ダメージ」とか「所持金 5000兆」といった、画面を埋め尽くすほどの桁数を見たことはありますか?
私自身はX(旧 Twitter)やYouTubeでたびたびネタにされている風景を時々見かけていました。
このようなインフレ数値が世間的に話題になったゲームとして最も顕著なのは、『ガンダムトライヴ』3というPCブラウザゲームかなと思います。本ゲームはサービス終了時点で、以下の画像のレベルまで数値がインフレしていました。
本題に戻しますと、
私たちプログラマが普段使う変数の型(int や long)には明確な限界があります。しかし、これらのゲームはその限界を遥かに超えてインフレし続けます。
一体どうやってあの天文学的な数値を格納・計算しているのか?
オーバーフローせずに無限に強くなれる仕組みについて、技術的なアプローチと「最適解」を調べてみました。
今回は、開発環境の例としてC#(Unity)を挙げて紹介します。
まずは標準的な型の限界を知る
C#(Unity)で扱う一般的な整数型には、以下の限界があります。
| 型 | ビット数 | 最大値 | およその数 |
|---|---|---|---|
| int | 32bit | $2,147,483,647$ | 約 20億 |
| long | 64bit | $9,223,372,036,854,775,807$ | 約 900京 |
「900京あれば十分では?」 と思うかもしれませんが、インフレ系ゲームの世界では「1秒毎に 9999無量大数<無量大数> ダメージ」といった爆発的な数値や「毎秒生産力が10倍になる」といった指数関数的な成長をするため、long であっても数分〜数時間でカンスト(オーバーフロー)してしまいます。
では、開発者たちはどうしているのでしょうか。主に3つのアプローチが見つかりました。
実装例①:double型(浮動小数点数)を使う
最も手軽かつ最初に思いつくのが double を使う方法です。
double は最大で約 $1.7 \times 10^{308}$ (308桁)という巨大な数を扱えます。
仕組み
コンピュータの標準規格である IEEE 754 に基づき、数値を「仮数部(有効数字)」と「指数部(桁の大きさ)」に分けて管理しています。
$$\text{値} = \text{仮数} \times 2^{\text{指数}}$$
問題点:精度の欠落
double型なら、通常のゲームプレイでオーバーフローすることは稀です。
ただ、巨大な数を扱える反面、「精度」に問題が出ます。
桁が大きくなりすぎると、小さな数の加減算が無視される「情報落ち」が発生します。
例: 100京のHPを持つボスに「1ダメージ」を与えても、計算上は誤差として切り捨てられ、HPが減らない。
これではRPGのような厳密な計算には向きません。
実装例②:BigIntegerを使う
C#には標準で System.Numerics.BigInteger という構造体が用意されているそうです。
仕組み
メモリが許す限り、ビットを継ぎ足して無限の桁数を表現できる型です。
理論上、上限はないそうです。
問題点:パフォーマンス
非常に強力ですが、計算コストが高いのが難点です。
また、計算のたびにメモリ確保(GC Allocation)が発生しやすいため、毎フレーム何千体もの敵や弾が飛び交うゲームで使うと、フレームレート低下の原因になりかねません。
実装例③:独自の「巨大数構造体」を作る
この手法は、「仮数部と指数部を自前で管理する構造体を作る」という方法です。double の仕組みを応用しつつ、指数部を long にすることで、double の限界(308桁)すら突破します。
概念的な実装イメージ
public struct BigNumber
{
// 仮数部:数字の並び(例: 1.23)
public double Mantissa;
// 指数部:桁数(例: 500乗)
public long Exponent;
// つまりこれは 1.23 x 10^500 を表現する
}
これで、指数部が long の限界(900京)に達するまで、つまり $10^{9,000,000,000,000,000,000}$ という、宇宙の原子の数すら比較にならないほどの数値を扱えるようになります。
概念としてはシンプルですが、これを実用レベル(四則演算や比較ができる状態)まで持っていくには、大量の演算子オーバーロードを実装する必要があるそうです。
専用のライブラリについて
以上より、「よし、構造体を自作しよう!」と思うところですが、前述の通りバグの温床になります。
調べてみると、この分野にはデファクトスタンダードとなるライブラリが存在しました。
BreakInfinity.cs
名作インフレゲーム『Antimatter Dimensions』4で使用されたJavaScriptライブラリを、C# (Unity) 用に移植したものです。
GitHub: razenpok/BreakInfinity.cs
特徴:
- 超巨大な数値: 最大で $10^{10^{308}}$ 程度まで扱えます。
- 高速: GC(ガベージコレクション)が発生しないよう最適化されており、毎フレームの計算に耐えられます。
-
使いやすい: 通常の
intやdoubleと同じ感覚で+-*/が使えます。
使い方
using BreakInfinity;
// 10の100乗を定義
var damage = new BigDouble(1, 100);
// 2倍にする
damage = damage * 2;
Debug.Log(damage);
// 出力: 2e100 (2かける10の100乗)
まとめ
インフレ系ゲームの裏側では、単なる変数の型ではなく、「仮数と指数を分けて管理する」という工夫が行われていました。
-
int/longはすぐに限界が来る -
BigIntegerは重すぎる - 仮数(double) + 指数(long) の組み合わせが最適解
- Unityで実装するなら
BreakInfinity.csがおすすめ
この記事が必要となる場面は極めて稀かと思いますが、
もし将来、「攻撃力 無量大数」を超えるゲームを作りたくなったら、このライブラリのことを思い出してみてください。
私について
普段は、ゲーム開発やWeb開発をしています!
興味を持っていただけた方は、以下のリンクに遊びに行ってみてください。
参考文献
1. 浮動小数点数(double)の限界と誤差について
➀【Unity】floatの計算で誤差が出る理由をビット列で見てみよう【C#】 - エクスプラボ
なぜ floatや double で計算すると「0.0000001」のような誤差が出るのか、ビットの仕組みから分かりやすく解説されています。
➁ 浮動小数点数の限界を把握する - Qiita
ゲーム開発において、座標や時間の計算で浮動小数点数がどのような問題を引き起こすか、実戦的な視点でまとめられています。
2. インフレ系ゲームの数値表現について
➀ C#で巨大な数を扱う - 鳩でもわかるC#
BigInteger型についての参考記事です。
➁ Unity、インフレクリッカー系ゲームによくある200kとか4.21mみたいな数値表示をしてみた - naichilab
内部的な数値管理だけでなく、「1.23a(1230京)」のように単位を変換して表示するテクニックについて解説されています。
➂ Cookie Clicker 日本語Wiki - 施設(Buildings)
インフレゲームの金字塔「クッキークリッカー」において、どのように生産コストが跳ね上がっていくか(指数関数的な増加)の仕様を確認できます。
3. ライブラリの実装(ソースコード)
➀ BreakInfinity.cs - GitHub
今回紹介したライブラリの本家リポジトリです。READMEは英語ですが、コード自体はC#なので読み解く際の参考にしました。
4. 類似記事
➀ 【9999無量大数<無量大数>ダメージ】桁の暴力でぶん殴るゲームの中身を冷静に考察する - Qiita
本記事の内容について、『ガンダムトライヴ』の視点から考察した記事です。
