Performance Tips
性能是將多人遊戲組件流暢、無縫地整合到您的應用程序中的重要部分。
因此,我們整理了一份使用Photon開發時應注意的提示清單。
定期調用服務
客戶端資料庫只有在應用邏輯觸發時才會發送任何消息。這樣一來,客戶端就可以聚合多個操作,避免網絡過度負載。
為了觸發發送任何數據,主循環必須經常調用LoadBalancingClient.Service()
或PhotonPeer.SendOutgoingCommands()
。如果一些數據仍在排隊,則bool返回值為ture。若是如此,再次調用SendOutgoingCommands(但不能連續超過三次)。
服務和SendOutgoingCommands也發送確認和ping,這對保持連接的動力很重要。你應該避免在調用兩者之間出現較長的停頓。特別是,要確保Service在加載時仍被調用。
如果忽略了,這個問題就很難識別和重現。C#資料庫有一個ConnectionHandler類,它可以提供幫助。
為了避免本地停滯,你可以在遊戲循環寫完網絡更新後調用SendOutgoingCommands。
更新與流量
加大每秒更新的數量可以使遊戲更加流暢和最新。另一方面,流量可能會急劇增加。另外,隨機的停滯和損失是無法避免的,所以更新的接收端應是能夠插入重要數值。
請記住,您調用的許多操作將為其他玩家創造事件,事實上,每秒鐘發送較少的更新可能會更快。
優化流量
You can usually send less to avoid traffic issues.
Doing so has a lot of different approaches:
Don't Send More Than What's Needed
Exchange only what's totally necessary.
Send only relevant values and derive as much as you can from them.
Optimize what you send based on the context.
Try to think about what you send and how often.
Non critical data should be either recomputed on the receiving side based on the data synchronized or with what's happening in game instead of forced via synchronization.
Examples:
In an RTS, you could send "orders" for a bunch of units when they happen.
This is much leaner than sending position, rotation and velocity for each unit ten times a second.
Good read: 1500 archers.In a shooter, send a shot as position and direction.
Bullets generally fly in a straight line, so you don't have to send individual positions every 100 ms.
You can clean up a bullet when it hits anything or after it travelled "so many" units.Don't send animations. Usually you can derive all animations from input and actions a player does.
There is a good chance that a sent animation gets delayed and playing it too late usually looks awkward anyways.Use delta compression. Send only values when they changes since last time they were sent.
Use interpolation of data to smooth values on the receiving side.
It's preferable over brute force synchronization and will save traffic.
Don't Send Too Much
Optimize exchanged types and data structures.
Examples:
- Make use of bytes instead of ints for small ints, make use of ints instead of floats where possible.
- Avoid exchanging strings at all costs and prefer enums/bytes instead.
- Avoid exchanging custom types unless you are totally sure about what get sent.
Use another service to download static or bigger data (e.g. maps).
Photon is not built as content delivery system.
It's often cheaper and easier to maintain to use HTTP-based content systems.
Anything that's bigger than the Maximum Transfer Unit (MTU) will be fragmented and sent as multiple reliable packages (they have to arrive to assemble the full message again).
Don't Send Too Often
Lower the send rate, you should go under 10 if possible.
This depends on your gameplay of course.
This has a major impact on traffic.
You can also use adaptive or dynamic send rate based on the user's activity or the exchanged data, this is also helping a lot.Send unreliable when possible.
You can use unreliable messages in most cases if you have to send another update as soon as possible.
Unreliable messages never cause a repeat.
Example: In an FPS, player position can usually be sent unreliable.
產生和消耗數據
與 "流量 "主題相關的問題是,只生產可以在接收端消耗的數據量。
如果性能或幀率跟不上傳入的事件,它們在執行之前就已經過時了。
在最壞的情況下,一方產生如此多的數據,以至於破壞了接收端。
在開發時要注意您的客戶端的隊列長度。
限制不可靠的命令的執行
即使一個客戶端有一段時間沒有調度傳入的消息(例如在加載時),它仍然會接收和緩沖所有的東西。
根據其他玩家的活動情況,客戶端可能會有很多東西需要跟進。
為了保持精簡,客戶端會自動將不可靠的消息削減到一定長度。
這樣做的目的是讓您更快地得到最新的信息,缺失的更新信息將很快被新的、最新的信息所取代。
這個限制是通過LoadbalancingPeer.LimitOfUnreliableCommands
設置的,默認為20(在PUN中也是如此)。
數據報大小
數據報的內容大小默認限制為1200字節。
這1200字節包括所有來自報頭(見"二進制協議")、大小和類型信息(見"Photon中的序列化")的過度負載,因此實際純有效載荷的數字會低一些。
事實上,即使它因數據結構的不同而不同,我們也可以有把握地認為,低於1kb的純有效載荷數據可以裝入一個數據報。
大於1200字節的操作和事件會被分割成碎片,並在多個命令中發送。
這些會自動變得可靠,接收方只有在收到所有碎片後才能重新組合和分配這些更大的數據塊。
更大的數據 "流 "會大大影響延遲,因為它們在發送前需要從許多包中重新組裝。
它們可以在一個單獨的通道中發送,所以它們不會影響一個(較低)通道號碼的 "實時 "位置更新。
Reduce Allocations With Pooled ByteArraySlice
By default, Photon clients in C# SDKs serializes byte[]
and ArraySegment<byte>
as byte[]
.
On the receiving side, this allocates a new byte[]
of the same length, which is passed to the OnEvent
callbacks.
ByteArraySlice is a non-alloc / non-boxing alternative to these options.
ByteArraySlice
is a wrapper class for a byte[]
very similar to ArraySegment<byte>
, except that it is a recyclable class.
As a class it can be cast to object (which all Photon messages are cast to) without creating an allocation from boxing.
The fields/properties of ByteArraySlice
are:
Buffer
: The wrappedbyte[]
array.Offset
: The starting byte the transport will read from.Count
: The number of bytes that were written past the Offset.
Serialization Usage
This can be done in of two ways:
Acquire from ByteArraySlicePool
C#
void Serialization()
{
// Get a pooled Slice.
var pool = loadBalancingClient.LoadBalancingPeer.ByteArraySlicePool;
var slice = pool.Acquire(256);
// Write your serialization to the byte[] Buffer.
// Set Count to the number of bytes written.
slice.Count = MySerialization(slice.Buffer);
loadBalancingClient.OpRaiseEvent(MSG_ID, slice, opts, sendOpts);
// The ByteArraySlice that was Acquired is automatically returned to the pool
// inside of the OpRaiseEvent
}
Maintain your own ByteArraySlice
C#
private ByteArraySlice slice = new ByteArraySlice(new byte[1024]);
void Serialization()
{
// Write your serialization to the byte[] Buffer.
// Set Count to the number of bytes written.
slice.Count = MySerialization(slice.Buffer);
loadBalancingClient.OpRaiseEvent(MSG_ID, slice, opts, sendOpts);
}
Deserialization Usage
By default byte[]
data is deserialized to new byte[x]
.
We must set LoadBalancingPeer.UseByteArraySlicePoolForEvents = true
to enable the non-alloc conduit.
Once enabled, we cast incoming objects to ByteArraySlice
rather than byte[]
.
C#
// By default byte arrays arrive as byte[]
// UseByateArraySlicePoolForEvents must be enabled to use this feature
private static void EnableByteArraySlicePooling()
{
loadBalancingPeer.UseByteArraySlicePoolForEvents = true;
}
private void OnEvent(EventData photonEvent)
{
// Rather than casting to byte[], we now cast to ByteArraySlice
ByteArraySlice slice = photonEvent.CustomData as ByteArraySlice;
// Read in the contents of the byte[] Buffer
// Your custom deserialization code for byte[] will go here.
Deserialize(slice.Buffer, slice.Count);
// Be sure to release the slice back to the pool
slice.Release();
}
重復使用事件數據
C#客戶端通過OnEvent(EventData ev)
接收事件。默認情況下,每個EventData都是一個新的實例,這給垃圾收集器帶來一些額外的工作。
在許多情況下,可以很容易地重復使用EventData並避免過度負載。這可以通過PhotonPeer.ReuseEventInstance
設置來啟用。