固定小数点
はじめに
Quantumでは、FP
構造体(固定小数点)が float
および double
のすべての使用を完全に置き換え、クロスプラットフォームの決定論を保証します。これは、FPVector2、FPVector3、FPMatrix、FPQuaternion、RNGSession、FPBounds2などの一般的な数学データ構造のバージョンを提供します。Quantum内のすべてのシステム(物理やナビゲーションを含む)は、計算に FP
値を独占的に使用します。
Quantumで実装された固定小数点型は Q48.16
です。これは、精度とパフォーマンスの良いバランスを提供しますが、後者にやや偏っています。
内部的に FP
は、結合された固定小数点数(整数部 + 小数部)を表すために1つの long を使用します。この long 値には FP.RawValue
を介してアクセスおよび設定できます。
QuantumのFP数学は、迅速な三角関数および平方根関数のために慎重に調整されたルックアップテーブルを使用しています(Assets/Photon/Quantum/Resources/LUT
を参照)。
FPの解析
表現可能な FP
の分数は限られており、double
ほど精度が高くありません。解析は近似であり、最も近い可能な FP
精度に丸められます。これは次の点に反映されます:
FP
としてfloat
の1.1f
を解析し、その後再びfloat
に変換すると、1.09999999f
になる可能性があります。- 異なる解析メソッドが同じマシンで異なる結果を生じる可能性があります。
C#
FP.FromFloat_UNSAFE(1.1f).RawValue != FP.FromString("1.1").RawValue
TLDR
- エディット時にのみ float から使用し、シミュレーションやランタイム内では決して使用しないでください。
- 可能な限り raw から変換する方が良いです。
FP.FromFloat_UNSAFE()
float
からの変換は、丸め誤差のため決定論的ではなく、シミュレーション内で決して行ってはいけません。シミュレーション内でこの変換を行うと、100%の確率で非同期問題が発生します。
ただし、編集またはビルド時には使用可能で、変換された(FP)データが最初に作成され、その後皆と共有されます。重要: この方法で生成されたデータは、異なるマシンでは互換性がない場合があります。
C#
var v = FP.FromFloat_UNSAFE(1.1f);
FP.FromString_UNSAFE()
これは内部的に string
を float
として解析し、その後 FP
に変換します。FromFloat_UNSAFE()
からのすべての注意点がここにも適用されます。
C#
var v = FP.FromFloat_UNSAFE("1.1");
FP.FromString()
これは決定論的であり、したがってどこでも安全に使用できますが、最もパフォーマンスが良いとは限りません。典型的なユースケースは、クライアントがサーバーからロードし、その後Quantumアセット内のデータを更新するために使用するバランシング情報(パッチ)です。
文字列のロケールに注意してください!小数点のための英語の数値形式のみを解析し、ドットが必要です(例:1000.01f)。
C#
var v = FP.FromFloat("1.1");
FP.FromRaw()
これはセキュアで高速です。内部表現を模倣します。
C#
var v = FP.FromRaw(72089);
このスニペットは、Unity内でFPコンバータウィンドウを作成し、便利な変換を行うために使用できます。
C#
using System;
using UnityEditor;
using Photon.Deterministic;
public class FPConverter : EditorWindow {
private float _f;
private FP _fp;
[MenuItem("Quantum/FP Converter")]
public static void ShowWindow() {
GetWindow(typeof(FPConverter), false, "FP Converter");
}
public virtual void OnGUI() {
_f = EditorGUILayout.FloatField("Input", _f);
try {
_fp = FP.FromFloat_UNSAFE(_f);
var f = _fp.AsFloat;
var rect = EditorGUILayout.GetControlRect(true);
EditorGUI.FloatField(rect, "Output FP", f);
QuantumEditorGUI.Overlay(rect, "(FP)");
EditorGUILayout.LongField("Output Raw", _fp.RawValue);
}
catch (OverflowException e) {
EditorGUILayout.LabelField("Out of range");
}
}
}
定数変数
FP
は構造体であるため、定数として使用することはできません。しかし、const
変数に "FP
" 値をハードコードして使用することは可能です:
事前定義された
FP._1
static
ゲッターまたはFP.Raw._1
const
変数を組み合わせます。C#
FP foo = FP._1 + FP._0_10; // または foo.RawValue = FP.Raw._1 + FP.Raw._0_10;
C#
const long MagicNumber = FP.Raw._1 + FP.Raw._0_10; FP foo = default; foo.RawValue = MagicNumber; // または foo = FP.FromRaw(MagicNumber);
特定の float を一度 FP に変換し、その生の値を定数として保存します。
C#
const long MagicNumber = 72089; // 1.1 var foo = FP.FromRaw(MagicNumber); // または foo.RawValue = MagicNumber;
Quantum DSL 内で定数を定義します。
#define FPConst 1.1
次のように使用します:
C#
var foo = default(FP); foo += Constants.FPConst; // または foo.RawValue += Constants.Raw.FPConst;
コードは以下のように定数を表現するために生成されます:
C#
public static unsafe partial class Constants { public const Int32 PLAYER_COUNT = 8; /// <summary>1.100006</summary> public static FP FPConst { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { FP result; result.RawValue = 72090; return result; } } public static unsafe partial class Raw { /// <summary>1.100006</summary> public const Int64 FPConst = 72090; } }
クラス内で
readonly
のstatic
変数を定義します。C#
private readonly static FP MagicNumber = FP._1 + FP._0_10;
const
変数に比べてパフォーマンスにペナルティがあるため、readonly
でマークするのを忘れないでください。ランタイム中に値をランダムに変更することは、非同期問題に至る可能性があります。
キャスティング
int
、uint
、short
、ushort
、byte
、sbyte
から FP
への暗黙のキャスティングは許可されており、安全です。
C#
FP v = (FP)1;
FP v = 1;
FP
から float
または double
への明示的キャスティングは可能ですが、当然ながらシミュレーション内では使用するべきではありません。
C#
var v = (double)FP._1;
var v = (float)FP._1;
整数へのキャスティングとその後の戻りは安全です。
C#
FP v = (FP)(int)FP._1;
不安全なキャストは [obsolete]
としてマークされ、InvalidOperationException
を引き起こします。
C#
FP v = 1.1f; // エラー
FP v = 1.1d; // エラー
インライン
Quantumのすべての低レベルシステムは、可能な限りパフォーマンスを引き出すために手動でインラインされた FP
算術を使用します。固定小数点数学では整数の除算と乗算を使用します。これを達成するために、結果または被除数は、計算の前または後に FP-Precision (16)でビットシフトされます。
C#
var v = parameter * FP._0_01;
// インライン整数数学
FP v = default;
v.RawValue = (parameter.RawValue * FP._0_01.RawValue) >> FPLut.PRECISION;
C#
var v = parameter / FP._0_01;
// インライン整数数学
FP v = default;
v.RawValue = (parameter.RawValue << FPLut.PRECISION) / FP._0_01.RawValue;
オーバーフロー
FP.UseableMax
は、自己と乗算してオーバーフローを引き起こさない最大の FP
数を表します(long
の範囲を超えることはありません)。
FP.UseableMax
Decimal: 32767.9999847412
Raw: 2147483647
Binary: 1111111111111111111111111111111 = 31 bit
FP.UseableMin
Decimal: -32768
Raw: -2147483648
Binary: 10000000000000000000000000000000 = 32 bit
精度
一般的な FP
精度は、数値が特定の範囲 (0.01..1000)
に保たれるときに十分です。FP数学に関連する精度の問題は通常、不正確な結果を生み出し、数学に基づくシステムを不安定にする傾向があります。非常に一般的なケースは、非常に高いまたは小さい数値を乗算し、その後除算によって元の範囲に戻ることです。結果として得られる数値は精度を失います。
次の例は、ClosestDistanceToTriangle
からのメソッド抜粋です。ここでは t0
が2つのドット積を乗算して計算され、そのドット積もすでに乗算から得られるものであるため、非常に正確な結果が求められるときに問題となります。この問題を軽減する方法は、計算前に値を人工的にシフトし、結果を戻すことです。これは、入力の範囲がある程度知られている場合に有効です。
C#
var diff = p - v0;
var edge0 = v1 - v0;
var edge1 = v2 - v0;
var a00 = Dot(edge0, edge0);
var a01 = Dot(edge0, edge1);
var a11 = Dot(edge1, edge1);
var b0 = -Dot(diff, edge0);
var b1 = -Dot(diff, edge1);
var t0 = a01 * b1 - a11 * b0;
var t1 = a01 * b0 - a00 * b1;
// ...
closestPoint = v0 + t0 * edge0 + t1 * edge1;
FPAnimationCurves
Unityには、いくつかの値を曲線の形で表現するために便利なツールがあります。これには、これらの曲線にカスタムエディタが付属しており、その後シリアライズされ、特定のポイントで曲線の値を評価するためにランタイムで使用できます。
曲線は、車両を実装する際のステアリング情報を表現したり(Bot SDKのユーティリティ理論のように)、AIエージェントの意思決定時のユーティリティ値を表現したり、攻撃のダメージに対する乗数値を取得したりするなど、多くの状況で使用されます。
Quantum SDKには、FPAnimationCurve
と呼ばれる独自のアニメーション曲線の実装がすでに付属しており、FPとして評価されます。このカスタムタイプは、Unityのデータアセットやコンポーネントで直接検査される際に、Unityのデフォルトのアニメーション曲線エディタを使用して描画され、そのデータは内部的に決定論的な型に焼き付けられます。
FPAnimationCurveからデータをポーリングする
Quantumコードを使用して、曲線からデータをポーリングするために必要なコードは、Unityのバージョンと非常に似ています:
C#
// これは事前にベイクされた値を返し、曲線の構成(キー点、曲線の解像度、接線モードなど)に応じて補間されます。
FP myValue = myCurve.Evaluate(FP._0_50);
シミュレーション上でFPAnimationCurvesを直接作成する
以下は、シミュレーション上でゼロから決定論的なアニメーション曲線を作成するためのスニペットです:
C#
// 5つのキー点を持つ単純な線形曲線を作成
// パラメータを好みに応じて変更
public static class FPAnimationCurveUtils {
public static FPAnimationCurve CreateLinearCurve(FPAnimationCurve.WrapMode preWrapMode, FPAnimationCurve.WrapMode postWrapMode) {
return new FPAnimationCurve {
Samples = new FP[5] { FP._0, FP._0_25, FP._0_50, FP._0_75, FP._1 },
PostWrapMode = (int)postWrapMode,
PreWrapMode = (int)preWrapMode,
StartTime = 0,
EndTime = 1,
Resolution = 32
};
}
}
C#
// ローカル変数に曲線を保存
var curve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);
// アセット内に曲線を初期化するために直接使用することもできます
public unsafe partial class CollectibleData {
public FPAnimationCurve myCurve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);
}
UnityのAnimationCurveをFPAnimationCurveにベイクする
通常のUnityの AnimationCurve
を FPAnimationCurve
に変換するために必要なコードスニペットは、こちら で見つけることができます。
他に翻訳が必要な部分があればお知らせください!
Back to top