Fixed Point
概述
在Quantum中,FP
架構(固定點)完全地取代了所有floats
及doubles
的使用,以確保跨平台的確定性。它提供常用數學資料架構的版本,比如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
精確度。這反映在:
- 剖析一個
FP
為float
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
是一個架構,並且因此不能被作為一個常數來使用。然而,可以硬式編碼並且在const
變數中使用「FP
」值:
- 結合預先定義的
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);
- 轉換一次特定浮點數到FP,並且以一個常數來儲存原始值
C#
const long MagicNumber = 72089; // 1.1
var foo = FP.FromRaw(MagicNumber);
// or
foo.RawValue = MagicNumber;
- 在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;
}
}
- 在類別中定義
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; // 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
所需的程式碼可在此找到:請按此處。