|
|
(One intermediate revision by the same user not shown) |
Line 1: |
Line 1: |
| {{subst:#if:|||{{LAuto/t}}
| | {{wip}}{{translate}} |
| | |
| --- DON'T JUST BLINDLY DELETE THIS PART. DO REPLACE THE LINKS AND CATEGORIES. THE PICTURE SHOWS HOW TO USE IT ! ---
| |
| | |
| SEARCH FOR:
| |
| \[\[(?!#|File(?:[ _]talk)?:|Image(?:[ _]talk)?:|Media:|Template(?:[ _]talk)?:|MediaWiki(?:[ _]talk)?:|Talk:|Category[ _]talk:|Project[ _]talk:|Valve[ _]Developer[ _]Community[ _]talk:|Help[ _]talk:|User(?:[ _]talk)?:|c:|commons:|Dictionary:|Google:|GoogleGroups:|IMDB:|M:|Meta:|Metawikipedia:|MW:|SdkBug:|SourceForge:|Steampowered:|W:|Wiki:|WikiBooks:|Wikipedia:|Wikiquote:|Wiktionary:|WP:)(:?(?:Category|Category|Help|Project|Valve[ _]Developer[ _]Community|Special|)(?:[^\|\]]+))(\|?.*?)\]\]
| |
| | |
| REPLACE WITH:
| |
| {{subst:LAuto|$1$2}}
| |
| | |
| }}{{wip}}{{translate}}
| |
| {{LanguageBar}} | | {{LanguageBar}} |
|
| |
|
| |
|
| {{toc-right}} | | {{toc-right}} |
|
| |
|
| '''Prediction''' is the notion of the client predicting the effects of the local player's actions without waiting for the server to confirm them. An entity's predicted state is tested against server commands as they arrive until either a match or a mis-match is detected. | | '''预测(Prediction)'''是客户端在无需等待服务器确认的情况下,预先模拟本地玩家操作效果的技术。实体的预测状态会与接收到的服务器指令进行比对,直到检测到匹配或失配。 |
|
| |
|
| In the vast majority of cases the client's prediction is confirmed by the server and it continues happily as if there was no latency. If there is a mis-match, which is rare if the prediction code is written correctly, then the client goes back and re-simulates all of the commands it ran with bad data. Depending on the severity of the error this can cause a noticeable hitch in the player's position and state, and possibly the state of the world too.
| | 在绝大多数情况下,服务器的确认会使客户端的预测得以延续,整个过程流畅得仿佛没有延迟。若出现失配(在预测代码正确编写的情况下较为罕见),客户端将回溯并使用修正后的数据重新模拟所有指令。根据错误严重程度,可能导致玩家位置、状态甚至世界状态出现明显卡顿。 |
|
| |
|
| Prediction is closely linked to [[lag compensation]], but is separate from [[interpolation]]. It only exists on the client.
| | 预测与{{L|lag compensation|延迟补偿}}紧密相关,但独立于{{L|interpolation|插值}}。预测仅存在于客户端。 |
|
| |
|
| {{warning|Don't under any circumstances be tempted into thinking "these two values won't ever go out of sync". There is ''always'' the possibility of packet loss!}} | | {{warning|切勿抱有“这两个值永远不会不同步”的侥幸心理。数据包丢失的可能性始终存在!}} |
|
| |
|
| {{note|Because only [[CBasePlayer|players]] are lag compensated, attempts to hit other entities (not least NPCs) do not benefit from prediction - you still have to lead your aim by latency. If this is an issue, consider [[NPC Lag Compensation|lag compensating your NPCs]]. (In [[Left 4 Dead 2]], [[prop_physics]] can be selectively lag compensated.)}} | | {{note|由于只有{{L|CBasePlayer|玩家实体}}支持延迟补偿,攻击其他实体(尤其是NPC)时无法受益于预测——仍需根据延迟提前瞄准。若需解决此问题,可考虑{{L|NPC Lag Compensation|为NPC启用延迟补偿}}(在{{L|Left 4 Dead 2}}中,{{L|prop_physics}}可选择性启用延迟补偿)。}} |
|
| |
|
| == Implementation == | | == 实现方法 == |
|
| |
|
| To predict an entity:
| | 要实现实体预测: |
|
| |
|
| # It should be '''manipulable by the local player'''. Otherwise, what is the point in predicting it? | | # 实体必须能被本地玩家操控,否则预测无意义 |
| # The functions that are to be predicted must '''exist and be identical on both the client and server'''. This is achieved by making them [[shared code]]. | | # 需预测的功能必须在客户端和服务器端同时存在且完全一致,可通过{{L|shared code|共享代码}}实现 |
| # The entity must make the call '''<code>SetPredictionEligible(true)</code>''', preferably in its constructor. | | # 实体需调用'''<code>SetPredictionEligible(true)</code>'''(建议在构造函数中执行) |
| # It must have a '''<code>bool ShouldPredict()</code>''' function on the client. Check if the local player is holding this entity and so forth here. {{note|Weapons must also implement <code>bool IsPredicted()</code>, again on the client. It should return <code>true</code> all the time.}} | | # 客户端需实现'''<code>bool ShouldPredict()</code>'''函数,用于检测本地玩家是否持有该实体等条件{{note|武器还需在客户端实现<code>bool IsPredicted()</code>,始终返回<code>true</code>}} |
| # Any variables that must be synchronised between the client and server must be both [[Networking Entities|networked]] and present in the entity's prediction table. | | # 所有需同步的变量必须进行{{L|Networking Entities|网络传输}}并注册到实体的预测表中 |
|
| |
|
| === Prediction tables === | | === 预测表 === |
|
| |
|
| All client-side variables modified by predicted player input must be added to a prediction table. There are three behaviours you can choose between:
| | 所有受玩家输入影响的客户端变量必须加入预测表,可选择三种注册方式: |
|
| |
|
| ; <code>FTYPEDESC_INSENDTABLE</code> | | ; <code>FTYPEDESC_INSENDTABLE</code> |
| : This is a networked variable. The client's predicted value will be tested against received server values, and if the two go out of synch a prediction error will be created. | | : 网络传输变量。客户端预测值会与服务器值比对,若不同步将产生预测错误 |
| ; <code>FTYPEDESC_NOERRORCHECK</code> | | ; <code>FTYPEDESC_NOERRORCHECK</code> |
| : This is a predicted value that can go out of synch without generating a prediction error. It may or may not be networked. | | : 允许不同步的预测值(无论是否网络传输),不会触发预测错误 |
| ; <code>FTYPEDESC_PRIVATE</code> | | ; <code>FTYPEDESC_PRIVATE</code> |
| : This isn't predicted or networked at all, but is still registered so that it can inspected with <code>cl_pdump</code>. {{note|This type of variable won't be saved/restored when the prediction system winds time back to test against a new server command. If a predicted function increments it, the increment will be applied over and over again on the client as each new command is tested.}} | | : 既不预测也不传输,但仍注册以便通过<code>cl_pdump</code>查看{{note|此类变量在预测系统回溯时不会被保存/恢复。若预测函数修改其值,客户端在测试新指令时会持续累加该值}} |
|
| |
|
| These behaviours are implemented with the '''<code>DEFINE_PRED_FIELD()</code>''' and '''<code>DEFINE_PRED_FIELD_TOL()</code>''' macros. The latter allows you to specify a tolerance within which an int or float is allowed to differ without generating prediction errors. This is mainly intended for use when a value has been rounded prior to transmission: floats are often trimmed to 1ms resolution, for instance (the #define <code>TD_MSECTOLERANCE</code> exists especially for this situation - otherwise, you should pass a numeric value).
| | 通过'''<code>DEFINE_PRED_FIELD()</code>'''和'''<code>DEFINE_PRED_FIELD_TOL()</code>'''宏实现上述功能。后者可为整型/浮点型变量设置误差容忍范围,常用于处理传输前的舍入(例如浮点数常被截断为1ms精度,此时可使用专为此设计的<code>TD_MSECTOLERANCE</code>宏,否则需直接指定数值)。 |
|
| |
|
| Be cautious when extrapolating from a rounded figure in these circumstances. Small differences in the transmitted value can lead to large changes in the results of your calculations!
| | 注意:基于舍入值的推算需谨慎,传输值的微小差异可能导致计算结果大幅偏离! |
|
| |
|
| {{todo|<code>DEFINE_PRED_TYPEDESCRIPTION</code>}} | | {{todo|<code>DEFINE_PRED_TYPEDESCRIPTION</code>}} |
|
| |
|
| ==== Example ==== | | ==== 示例 ==== |
|
| |
|
| <source lang=cpp>#ifdef CLIENT_DLL | | <source lang=cpp>#ifdef CLIENT_DLL |
Line 62: |
Line 51: |
| #endif</source> | | #endif</source> |
|
| |
|
| === Predicting entity creation === | | === 预测实体创建 === |
|
| |
|
| The standard prediction system can maintain an existing entity's state, but it does not help with creating new entities. <code>[http://www.mail-archive.com/hlcoders@list.valvesoftware.com/msg09429.html CreatePredictedEntityByName()]</code> does. It creates a new entity on the client and swaps it for the real one when it arrives from the server. Needless to say, you should call it from [[shared code]].
| | 标准预测系统仅维护现有实体状态,创建新实体需使用<code>[http://www.mail-archive.com/hlcoders@list.valvesoftware.com/msg09429.html CreatePredictedEntityByName()]</code>。该函数在客户端创建临时实体,待服务器实体到达后进行替换。务必通过{{L|shared code}}调用。 |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
Line 70: |
Line 59: |
| </source> | | </source> |
|
| |
|
| Yes, you really do enter in the name of the file in which the class is declared, called <code>module</code> in the actual code, and its line number ({{confirm|which means that the entity must have a shared class declaration}}). They appear to be used to generate a fingerprint for the entity. <code>persist</code> causes the entity to be tested during reprediction.
| | 参数<code>class_file</code>需填写类声明所在文件名(代码中称为<code>module</code>),<code>line</code>为行号({{confirm|意味着实体必须有共享类声明}}),二者用于生成实体指纹。<code>persist</code>使实体参与重预测检测。 |
|
| |
|
| If the predicted entity contains data that was not sent to the server, you should copy it into the real entity in <code>C_BaseEntity::OnPredictedEntityRemove()</code>.
| | 若预测实体包含未传输数据,需在<code>C_BaseEntity::OnPredictedEntityRemove()</code>中将数据复制到真实实体。 |
|
| |
|
| == Tools and tricks == | | == 调试技巧 == |
|
| |
|
| === IsFirstTimePredicted() === | | === IsFirstTimePredicted() === |
|
| |
|
| Testing <code>prediction->IsFirstTimePredicted()</code> ensures that code is only executed when the client first predicts an action, and not when it checks against subsequent server updates. You must <code>#include "prediction.h"</code> to access it.
| | 通过<code>prediction->IsFirstTimePredicted()</code>确保代码仅在首次预测时执行,避免在服务器更新校验时重复执行。需<code>#include "prediction.h"</code>。 |
|
| |
|
| === SharedRandom() === | | === SharedRandom() === |
|
| |
|
| Use this function for random numbers. The seed it uses is based on the usercmd number, making the result identical on client and server.
| | 使用此函数生成随机数,其种子基于用户指令编号,保证客户端与服务器结果一致。 |
|
| |
|
| === CDisablePredictionFiltering === | | === CDisablePredictionFiltering === |
Line 88: |
Line 77: |
| {{todo}} | | {{todo}} |
|
| |
|
| === Suppressing network data === | | === 抑制网络事件 === |
|
| |
|
| Non-critical events like weapon effects can be done entirely on the client. Suppressing that data is a good idea as it results in reduced bandwidth usage.
| | 非关键事件(如武器特效)可完全在客户端处理,抑制相关网络传输能有效降低带宽。 |
|
| |
|
| <code>[[IPredictionSystem]]::SuppressHostEvents()</code> is meant for this. When it is called, all network data to the given player is halted. Sending NULL again will turn transmission back on. For example: | | <code>{{L|IPredictionSystem}}::SuppressHostEvents()</code>专为此设计。调用时会暂停向指定玩家发送网络事件,传入<code>NULL</code>恢复传输。例如: |
|
| |
|
| <source lang=cpp>if ( pPlayer->IsPredictingWeapons() ) | | <source lang=cpp>if ( pPlayer->IsPredictingWeapons() ) |
Line 101: |
Line 90: |
| IPredictionSystem::SuppressHostEvents( NULL );</source> | | IPredictionSystem::SuppressHostEvents( NULL );</source> |
|
| |
|
| Note that after doing the above you will need to write code somewhere else that makes the suppressed player's client generate the effects itself.
| | 注意:使用此方法后需编写代码让被抑制玩家的客户端自行生成特效。 |
|
| |
|
| == Sample == | | == 示例 == |
|
| |
|
| [[Prediction/Sample weapon|Here is a simple weapon that prints prediction information to the console.]] Use its rapid secondary fire with net_fakelag 200 (or above) to see how the engine handles multiple predicted shots.
| | {{L|Prediction/Sample weapon|此处提供可输出预测信息到控制台的简易武器}}。配合<code>net_fakelag 200</code>(或更高)使用快速次要开火,可观察引擎如何处理多预测射击。 |
|
| |
|
| {{note|The gun will appear jittery when lagged unless your player entity is creating a predicted viewmodel.}} | | {{note|若玩家实体未创建预测视图模型,延迟时武器会显示抖动。}} |
|
| |
|
| == Troubleshooting == | | == 故障排查 == |
|
| |
|
| If you add functionality to weapons and forget to follow the steps above, then an entity may jerk around or animate strangely. You can use <code>[[cl_predictionlist]]</code> and <code>[[cl_pdump]]</code> to debug this. Changing <code>[[cl_pred_optimize]]</code> can also help sometimes.
| | 若新增功能未遵循上述步骤,可能导致实体抖动或动画异常。可通过<code>{{L|cl_predictionlist}}</code>和<code>{{L|cl_pdump}}</code>调试。<code>{{L|cl_pred_optimize}}</code>有时也有帮助。 |
|
| |
|
| Now let's say you see a variable that turns red in the <code>cl_pdump</code> panel from time to time. Now you know that somehow the client is producing a different value for this variable than the server is. This can usually be traced down to one of the following problems:
| | 假设<code>cl_pdump</code>面板中某变量偶尔变红,说明客户端与服务器端该变量值不一致。常见原因: |
|
| |
|
| # The client is not running some code that the server is running. This would be the case if you had an <code>#ifdef GAME_DLL</code> or an <code>#ifndef CLIENT_DLL</code> around a piece of code that could affect the value of the variable that's turning red. | | # 客户端未运行部分服务器端代码(可能因<code>#ifdef GAME_DLL</code>或<code>#ifndef CLIENT_DLL</code>隔离了相关代码) |
| # Another variable that can wind up affecting the value of the red variable is not being transmitted via the data table. In this case, the client's value for that variable would always be wrong (since the server never transmitted it). | | # 影响该变量的其他变量未加入数据表传输(客户端值始终错误) |
| # It's also possible that you just forgot to add in the appropriate tolerance using the <code>DEFINE_PRED_FIELD_TOL</code> macro. For example, if you're transmitting a floating point variable with a value range of 0.0 - 255.0, and you only give it 4 bits of precision, then you're going to need a tolerance of about 17.0, or else the prediction system will think the variable's value is wrong when it is actually just different because the variable was compressed into 4 bits before sending it to the client. | | # 未使用<code>DEFINE_PRED_FIELD_TOL</code>设置适当容差(例如4位精度的0.0-255.0浮点数需约17.0的容差) |
|
| |
|
| Tracking down prediction problems is usually a matter of walking through any code that can affect the values that are turning red, and noticing which other variables affect their values. It can be tedious at first, but once you get the hang of it, you can track down prediction problems quickly.
| | 排查预测问题通常需要追溯影响变量值的所有代码路径,检查相关变量同步情况。初期可能繁琐,但掌握方法后可快速定位。 |
|
| |
|
| == See also == | | == 另见 == |
|
| |
|
| * [[Source Multiplayer Networking#Input prediction]] | | * {{L|Source Multiplayer Networking#Input prediction}} |
| * [[Lag compensation]] | | * {{L|Lag compensation}} |
| * [[Interpolation]] | | * {{L|Interpolation}} |
|
| |
|
| [[Category:Networking]]
| | {{ACategory|Networking}} |
| [[Category:Programming]]
| | {{ACategory|Programming}} |
This page is actively undergoing a major edit.As a courtesy, please do not edit this while this message is displayed.
If this page has not been edited for at least several hours to a few days, please remove this template. This message is intended to help reduce edit conflicts; please remove it between editing sessions to allow others to edit the page.
The person who added this notice will be listed in its edit history should you wish to contact them.
This page needs to be translated.This page either contains information that is only partially or incorrectly translated, or there isn't a translation yet.
If this page cannot be translated for some reason, or is left untranslated for an extended period of time after this notice is posted, the page should be requested to be deleted.
Also, please make sure the article complies with the
alternate languages guide.(en)
预测(Prediction)是客户端在无需等待服务器确认的情况下,预先模拟本地玩家操作效果的技术。实体的预测状态会与接收到的服务器指令进行比对,直到检测到匹配或失配。
在绝大多数情况下,服务器的确认会使客户端的预测得以延续,整个过程流畅得仿佛没有延迟。若出现失配(在预测代码正确编写的情况下较为罕见),客户端将回溯并使用修正后的数据重新模拟所有指令。根据错误严重程度,可能导致玩家位置、状态甚至世界状态出现明显卡顿。
预测与延迟补偿(en)紧密相关,但独立于插值(en)。预测仅存在于客户端。
警告:切勿抱有“这两个值永远不会不同步”的侥幸心理。数据包丢失的可能性始终存在!
实现方法
要实现实体预测:
- 实体必须能被本地玩家操控,否则预测无意义
- 需预测的功能必须在客户端和服务器端同时存在且完全一致,可通过共享代码(en)实现
- 实体需调用
SetPredictionEligible(true)
(建议在构造函数中执行)
- 客户端需实现
bool ShouldPredict()
函数,用于检测本地玩家是否持有该实体等条件
注意:武器还需在客户端实现bool IsPredicted()
,始终返回true
- 所有需同步的变量必须进行网络传输(en)并注册到实体的预测表中
预测表
所有受玩家输入影响的客户端变量必须加入预测表,可选择三种注册方式:
FTYPEDESC_INSENDTABLE
- 网络传输变量。客户端预测值会与服务器值比对,若不同步将产生预测错误
FTYPEDESC_NOERRORCHECK
- 允许不同步的预测值(无论是否网络传输),不会触发预测错误
FTYPEDESC_PRIVATE
- 既不预测也不传输,但仍注册以便通过
cl_pdump
查看
注意:此类变量在预测系统回溯时不会被保存/恢复。若预测函数修改其值,客户端在测试新指令时会持续累加该值
通过DEFINE_PRED_FIELD()
和DEFINE_PRED_FIELD_TOL()
宏实现上述功能。后者可为整型/浮点型变量设置误差容忍范围,常用于处理传输前的舍入(例如浮点数常被截断为1ms精度,此时可使用专为此设计的TD_MSECTOLERANCE
宏,否则需直接指定数值)。
注意:基于舍入值的推算需谨慎,传输值的微小差异可能导致计算结果大幅偏离!
待完善: DEFINE_PRED_TYPEDESCRIPTION
示例
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CBaseCombatWeapon )
DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_hOwner, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD_TOL( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
END_PREDICTION_DATA()
#endif
预测实体创建
标准预测系统仅维护现有实体状态,创建新实体需使用CreatePredictedEntityByName()
。该函数在客户端创建临时实体,待服务器实体到达后进行替换。务必通过shared code(en)调用。
CBaseEntity::CreatePredictedEntityByName( char* classname, char* class_file, int line, bool persist = false )
参数class_file
需填写类声明所在文件名(代码中称为module
),line
为行号(
证实:意味着实体必须有共享类声明),二者用于生成实体指纹。persist
使实体参与重预测检测。
若预测实体包含未传输数据,需在C_BaseEntity::OnPredictedEntityRemove()
中将数据复制到真实实体。
调试技巧
IsFirstTimePredicted()
通过prediction->IsFirstTimePredicted()
确保代码仅在首次预测时执行,避免在服务器更新校验时重复执行。需#include "prediction.h"
。
SharedRandom()
使用此函数生成随机数,其种子基于用户指令编号,保证客户端与服务器结果一致。
CDisablePredictionFiltering
[待完善]
抑制网络事件
非关键事件(如武器特效)可完全在客户端处理,抑制相关网络传输能有效降低带宽。
IPredictionSystem(en)::SuppressHostEvents()
专为此设计。调用时会暂停向指定玩家发送网络事件,传入NULL
恢复传输。例如:
if ( pPlayer->IsPredictingWeapons() )
IPredictionSystem::SuppressHostEvents( pPlayer );
pWeapon->CreateEffects();
IPredictionSystem::SuppressHostEvents( NULL );
注意:使用此方法后需编写代码让被抑制玩家的客户端自行生成特效。
示例
此处提供可输出预测信息到控制台的简易武器(en)。配合net_fakelag 200
(或更高)使用快速次要开火,可观察引擎如何处理多预测射击。
注意:若玩家实体未创建预测视图模型,延迟时武器会显示抖动。
故障排查
若新增功能未遵循上述步骤,可能导致实体抖动或动画异常。可通过cl_predictionlist(en)
和cl_pdump(en)
调试。cl_pred_optimize(en)
有时也有帮助。
假设cl_pdump
面板中某变量偶尔变红,说明客户端与服务器端该变量值不一致。常见原因:
- 客户端未运行部分服务器端代码(可能因
#ifdef GAME_DLL
或#ifndef CLIENT_DLL
隔离了相关代码)
- 影响该变量的其他变量未加入数据表传输(客户端值始终错误)
- 未使用
DEFINE_PRED_FIELD_TOL
设置适当容差(例如4位精度的0.0-255.0浮点数需约17.0的容差)
排查预测问题通常需要追溯影响变量值的所有代码路径,检查相关变量同步情况。初期可能繁琐,但掌握方法后可快速定位。
另见