quantum | v2 switch to V1  

固定小数点

イントロダクション

Quantum ではFP構造体 (Fixed Point) が floatsdoubles を完全に置き換え、クロスプラットフォームでの決定性を保証しています。Quantumは、FPVector2、FPVector3、FPMatrix、 FPQuaternion、RNGSession、FPBounds2などの一般的な数学データ構造のバージョンを提供します。物理学やナビゲーションなど、QuantumのすべてのシステムはFP値を演算時に独占的に使用します。

Quantumに実装されている固定小数点の種類は、Q48.16です。これは、精度と性能のバランスがよく、後者に比重を置いています。

内部的には、複合の固定小数点数(整数+小数部)を表すのに、FPは1つの長整数型を使用します。長整数型の値は、FP.RawValueでアクセスし設定できます。

QuantumのFP Mathは、高速な三角関数と平方根関数のために慎重に調整されたルックアップテーブルを使用します(QuantumSDK﹑Quantum_unity﹑Quantum®ResourcesLUT を参照してください)。

トップに戻る
 

FPの解析

表現可能なFPの分数は限られており、決してdoubleのように正確ではありません。解析は近似的なものであり、最も近いFPの精度に丸められます。これは、以下に反映されています:

FPfloat 1.1fとして解析してから、floatに変換しなおすと、1.09999999fとなる可能性があります。また、同じマシン上でも、解析方法が異なる場合には異なる結果となります。

    FP.FromFloat(1.1f).RawValue != FP.FromString("1.1").RawValue

トップに戻る
 

TLDR

  • floatからは編集時のみに使用し、シミュレーション内やランタイム時には使用しないでください。
  • 可能であれば常に、rawから変換するのが最善です。

トップに戻る
 

FP.FromFloat_UNSAFE()

floatからの変換は丸め誤差によって決定論的ではないため、シミュレーション内では決しておこなわないでください。シミュレーション内でおこなった場合には、必ず非同期が発生します。

ただし、編集 またはビルド時には、変換された(FP)データが最初に作成され、その後全員に共有される場合に、floatからの変換を使用できます。重要: この方法によって、異なる マシン上で生成されたデータには、互換性がない場合があります。

var v = FP.FromFloat_UNSAFE(1.1f);

トップに戻る
 

FP.FromString_UNSAFE()

これは内部的に、文字列をfloatとして解析し、FPに変換します。FromFloat_UNSAFE() のすべての注意事項が、ここにも該当します。

var v = FP.FromFloat_UNSAFE("1.1");

トップに戻る
 

FP.FromString()

これは決定論的であり、どこでも安全に使用できますが、最もパフォーマンスのすぐれたオプションではない可能性があります。典型的な使用例としては、クライアントがサーバーから読み込んだ 情報(パッチ)のバランスをとり、それを使用してQuantumアセット内のデータを更新する場合などがあります。

文字列のロケールに留意してください!これは、小数を表す英語の数値フォーマットのみを解析し、ドットを必要とします(例 1000.01f)。

var v = FP.FromFloat("1.1");

トップに戻る
 

FP.FromRaw()

これは、内部表現を模倣するため、安全かつ高速です。

var v = FP.FromRaw(72089);

以下のスニペットを使用すると、UnityでFPコンバーターウィンドウを作成し、便利な変換をおこなえます。

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 = FPPropertyDrawer.GetRawAsFloat(_fp.RawValue);
      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");
    }
  }
}

トップに戻る
 

Const変数

The `FP._1_10` syntax can not be extended or generated.

FPは構造体であるため、定数として使用することはできません。ただし、const変数で"FP"値をハードコードし使用することは可能です:

  1. あらかじめ定義されたFP._1``静的ゲッター、またはFP.Raw._1 const変数を組み合わせます。
  FP foo = FP._1 + FP._0_10;
  // or
  foo.RawValue = FP.Raw._1 + FP.Raw._0_10;
  const long MagicNumber = FP.Raw._1 + FP.Raw._0_10;

  FP foo = default;
  foo.RawValue = MagicNumber;
  // or
  foo = FP.FromRaw(MagicNumber);
  1. 特定のfloatをFPに一旦変換し、生の値を定数として保存します。
  const long MagicNumber = 72089; // 1.1

  var foo = FP.FromRaw(MagicNumber);
  // or
  foo.RawValue = MagicNumber;
  1. Quantum DSL内で定数を作成します。
  #define FPConst 1.1

そして、以下のように使用します:

  var foo = default(FP);
  foo += Constants.FPConst;
  // or
  foo.RawValue += Constants.Raw.FPConst;

定数を以下のように表すコードが生成されます:

  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;
    }
  }
  1. クラスに読み取り専用静的変数を定義します。
  private readonly static FP MagicNumber = FP._1 +  FP._0_10;

const 変数に比べて、パフォーマンスが低下します。ランタイム時に値をランダムに変更すると非同期になる可能性があるため、readonlyを指定するのを忘れないようにしてください。

トップに戻る
 

キャスティング

intuintshortushortbytesbyteからFPへの暗黙的なキャスティングは許容されており、かつ安全です。

FP v = (FP)1;
FP v = 1;

FPからfloatまたはdoubleへの明示的なキャスティングは可能ですが、シミュレーションでは当然使用するべきではありません。

var v = (double)FP._1;
var v = (float)FP._1;

ただし、integerやbackへのキャスティングは安全です。

FP v = (FP)(int)FP._1;
``

Unsafe casts are marked as `[obsolete]` and will cause a `InvalidOperationException`.
csharp

FP v = 1.1f; // ERROR FP v = 1.1d; // ERROR



トップに戻る
  ## インライン化 低レベルのQuantumシステムはすべて、可能な限り高いパフォーマンスを引き出すために、手動でインライン化した`FP`演算を使用しています。固定小数点演算は整数の除算と乗算を使用します。これを実現するため、計算前または後に、結果または被除数はFP-Precision (16)によってビットシフトされます。
csharp

var v = parameter * FP._0_01;

// inlined integer math FP v = default; v.RawValue = (parameter.RawValue * FP._0_01.RawValue) >> FPLut.PRECISION;

csharp

var v = parameter / FP._0_01;

// inlined integer math FP v = default; v.RawValue = (parameter.RawValue << FPLut.PRECISION) / FP._0_01.RawValue;



トップに戻る
  ## オーバーフロー `FP.UseableMax` は、それ自身と掛け合わせてもオーバーフローしない(`long`の範囲を超える)最大の`FP`数を表します。

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つのドットプロダクトを掛け合わせて計算されます。ドットプロダクトはすでに乗算の結果でもあります。これは、非常に正確な結果を期待する場合に問題となります。この問題を軽減する方法として、計算の前に値を人為的にシフトし、その結果をシフトし直す方法があります。これは、入力の範囲がある程度わかっている場合に有効です。
csharp

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には、ある値を曲線で表現するのに便利な一連のツールが付属しています。このような曲線のためのカスタムエディターが付属しており、それをシリアル化して、ある特定のポイントで曲線の値を評価するためにランタイム時に使用することができます。 カーブは、多くの場面で使用できます。たとえば、乗り物を実装する際のステアリング情報の表現、AIエージェントの意思決定時の効用価値(Bot SDKの効用理論で実施されます)、攻撃のダメージの乗算値取得などです。 Quantum SDKには、FPとして評価される`FPAnimationCurve`という名前の独自のアニメーションカーブが実装されています。このカスタムタイプは、Unityのデータアセットやコンポーネントで直接精査された場合に、Unityのデフォルトのアニメーションカーブ・エディターによって描画されます。このエディターのデータは、内部的に決定論の型にベイクされます。 トップに戻る
  ### FPAnimationCurveからデータをポーリング Quantumコードでカーブからデータをポーリングするのに必要なコードは、Unityのバージョンに非常によく似ています:
csharp

// This returns the pre-baked value, interpolated accordingly to the curve's configuration such as it's key points, curve's resolution, tangets modes, etc FP myValue = myCurve.Evaluate(FP._0_50);



トップに戻る
  ### シミュレーション上で、直接FPAnimationCurvesを作成 以下のスニペットでは、シミュレーション上で直接、決定論的アニメーションカーブを一から作成する方法を示しています:
csharp

// Creating a simple, linear curve with five key points // Change the parameter as prefered 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 }; } }

csharp

// Storing a curve into a local variable var curve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);

// It can also be used directly to pre-initialise a curve in an asset public unsafe partial class CollectibleData { public FPAnimationCurve myCurve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);



<a href="#main">トップに戻る</a><br>
<b id="216z6vj4tzwfmy2h">&nbsp;</b>
### UnityのAnimationCurveをFPAnimationCurveにベイキング


通常のUnity`AnimationCurve`を`FPAnimationCurve`に変換するために必要なスニペットは、[こちら](/ja-jp/quantum/current/concepts-and-patterns/animation-curves-baking)で参照してください。
<br>

ドキュメントのトップへ戻る