This document is about: QUANTUM 2
SWITCH TO

Fixed Point

概述

在Quantum中,FP架構(固定點)完全地取代了所有floatsdoubles的使用,以確保跨平台的確定性。它提供常用數學資料架構的版本,比如FPVector2、FPVector3、FPMatrix、FPQuaternion、RNGSession、FPBounds2等等。在Quantum中的所有系統,包含物理及導航,都在他們的計算中獨家使用FP值。

在Quantum中執行的固定點類型是Q48.16。它已證明是在精確性及性能之間的一個好的平衡,且較為偏向後者。

在內部,FP使用一個長值,以代表結合的固定點數字(整數+小數部分);長值可透過FP.RawValue來存取及設定。

Quantum的FP數學使用仔細調整的查閱表格,以快速得到三角函數或平方根函數(請參見QuantumSDK\quantum_unity\Assets\Photon\Quantum\Resources\LUT)。

剖析FP

可顯示的FP分數是受限制的,並且永不像一個double一樣準確。剖析是一個近似值,並且將四捨五入到最近的FP精確度。這反映在:

  • 剖析一個FPfloat 1.1f,並且接著轉換回float,可能會導致1.09999999f;而且,
  • 不同的剖析方法在同樣的機器上帶來不同的結果。

    C#

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

TLDR

  • 只在編輯的時候從浮點數使用,永不在模擬中或在運行時間使用。
  • 不論何時,最好是從原始來轉換。

FP.FromFloat_UNSAFE()

由於四捨五入產生的錯誤,從float轉換並不是確定性,並且應該在模擬之中被完成。在模擬之中做這樣一個轉換,將導致完全取消同步。

然而,當第一次建立已轉換(FP)資料並且之後共享給每個人的時候,在編輯組建時間時可以使用它。 重要事項:不同的機器上以此方式生成的資料可能不相容。

C#

var v = FP.FromFloat_UNSAFE(1.1f);

FP.FromString_UNSAFE()

這將內部地剖析string為一個浮點數,並且接著轉換到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 = 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變數

`FP._1_10`語法不能被擴展或生成。

FP是一個架構,並且因此不能被作為一個常數來使用。然而,可以硬式編碼並且在const變數中使用「FP」值:

  1. 結合預先定義的FP._1 static取得器或FP.Raw._1 const變數。

C#

FP foo = FP._1 + FP._0_10;
// or
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;
// or
foo = FP.FromRaw(MagicNumber);
  1. 轉換一次特定浮點數到FP,並且以一個常數來儲存原始值

C#

const long MagicNumber = 72089; // 1.1

var foo = FP.FromRaw(MagicNumber);
// or
foo.RawValue = MagicNumber;
  1. 在Quantum DSL中建立常數
#定義FPConst 1.1

然後如此使用:

C#

var foo = default(FP);
foo += Constants.FPConst;
// or
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;
  }
}
  1. 在類別中定義readonly static變數。

C#

private readonly static FP MagicNumber = FP._1 +  FP._0_10;

const變數相比,存在性能損失,並且請不要忘記去標記readonly,因為在運行階段隨機地改變值可能導致取消同步。

投放

intuintshortushortbytesbyte隱式投放到FP,是被允許且安全的。

C#

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

FP顯式投放到floatdouble,是可行的,但明顯地不應該在模擬中被使用。

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;  // ERROR
FP v = 1.1d;  // ERROR

內嵌

所有低層級Quantum系統都使用手動內嵌FP算術,以擷取所有可能的性能。固定點數學使用整數除法和乘法。為了實現這一點,在計算之前或之後,透過FP精確度(16),對結果或被除數進行位元移位。

C#

var v = parameter * FP._0_01;

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

C#

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
    小數:32767.9999847412
    原始:2147483647
    二進位:1111111111111111111111111111111 = 31位元
FP.UseableMin
    小數:-32768
    原始:-2147483648
    二進位:10000000000000000000000000000000 = 32位元

精確度

當數字被維持在一個特定的範圍(0.01..1000)時,一般的FP精確度是好的。與FP數學相關的精確度問題通常產生不準確的結果,並且傾向使(基於數學的)系統不穩定。一個非常常見的案例是去乘以非常大或非常小的數字,之後透過除法來返回原始範圍。結果數字將失去精確度。

另一個實例是這個摘要自ClosestDistanceToTriangle的方法。其中從兩個內積相乘來計算t0,而內積也已經是一個乘法的結果。當期待一個非常精確的結果時,這將是一個問題。一個避免這個問題的方法是在計算之前人工地將值移位,然後再將結果移位回來。當輸入的範圍某種程度上已知時,這將有效。

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的效用理論中完成),針對一些攻擊的傷害取得乘數值,以及其他功能。

Quantum SDK已經有一個動畫曲線的,其自己的執行方式,稱為FPAnimationCurve,其被評估為FP。這個自訂類型,當在資料資產上及在Unity中的元件上被直接偵測時,將以Unity的預設動畫曲線編輯器被畫成,其中的資料將接著被內部地內嵌到確定性類型之中。

從一個FPAnimationCurve輪詢資料

透過Quantum程式碼,從一個曲線輪詢一些資料所需的程式碼,是非常類似於Unity的版本:

C#

// 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

這裡是一些程式碼片段,以直接地在模擬上從頭開始建立一個確定性的動畫曲線:

C#

// 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
        };
    }
}

C#

// 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);

將Unity的動畫曲線內嵌到一個FPAnimationCurve

將一個常規的Unity AnimationCurve轉換成一個FPAnimationCurve所需的程式碼可在此找到:請按此處

Back to top