
TL; DR
Unity- GitHubã§ãã¬ã€ã€ãŒã®èº«äœã®åãã®ã¯ã©ã€ã¢ã³ãåŽã®äºæž¬ãå®è£ ããæ¹æ³ã瀺ããã¢ãäœæããŸããã
ã¯ããã«
2012幎ã®åãã«ãUnityã§ã®ãã¬ãŒã€ãŒã®ç©ççãªåãã®ã¯ã©ã€ã¢ã³ãåŽã§ã®äºæž¬ã®å®è£ æ¹æ³ã«é¢ããæçš¿ãæžããŸããã Physics.SimulateïŒïŒã®ãããã§ãç§ã説æããäžåšçšãªåé¿çã¯äžèŠã«ãªããŸããã å€ãæçš¿ã¯ä»ã§ãç§ã®ããã°ã§æã人æ°ã®ãããã®ã®1ã€ã§ãããçŸä»£ã®Unityã§ã¯ãã®æ å ±ã¯ãã§ã«ééã£ãŠããŸãã ãããã£ãŠã2018幎çããªãªãŒã¹ããŠããŸãã
ã¯ã©ã€ã¢ã³ãåŽã«ã¯äœããããŸããïŒ
競äºåã®ãããã«ããã¬ã€ã€ãŒã²ãŒã ã§ã¯ãå¯èœãªéãäžæ£è¡çºã¯é¿ããŠãã ããã éåžžãããã¯ãæš©åšäž»çŸ©çãªãµãŒããŒãåãããããã¯ãŒã¯ã¢ãã«ã䜿çšãããããšãæå³ããŸããã¯ã©ã€ã¢ã³ãã¯å ¥åãããæ å ±ããµãŒããŒã«éä¿¡ãããµãŒããŒã¯ãã®æ å ±ããã¬ãŒã€ãŒã®åãã«å€æãããã¬ãŒã€ãŒã®ã¹ããŒã¿ã¹ã®ã¹ãããã·ã§ãããã¯ã©ã€ã¢ã³ãã«éä¿¡ããŸãã ãã®å ŽåãããŒãæŒããŠããçµæã衚瀺ããããŸã§ã«é 延ããããã¢ã¯ãã£ããªã²ãŒã ã§ã¯åãå ¥ããããŸããã ã¯ã©ã€ã¢ã³ãåŽã®äºæž¬ã¯éåžžã«äžè¬çãªææ³ã§ãããé 延ãé ããçµæã®åããäºæž¬ããããã«ãã¬ãŒã€ãŒã«è¡šç€ºããŸãã ã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒããçµæãåä¿¡ãããšãã¯ã©ã€ã¢ã³ããäºæž¬ããçµæãšæ¯èŒããŸããçµæãç°ãªãå Žåãäºæž¬ã¯èª€ã£ãŠããããä¿®æ£ããå¿ èŠããããŸãã
ãµãŒããŒããåä¿¡ããã¹ãããã·ã§ããã¯ãåžžã«ã¯ã©ã€ã¢ã³ãã®äºæž¬ç¶æ ã«é¢ããŠéå»ã®ãã®ã§ãïŒããšãã°ãã¯ã©ã€ã¢ã³ããããµãŒããŒã«ããŒã¿ã転éããŠãã150ããªç§ãããå Žåãåã¹ãããã·ã§ããã¯å°ãªããšã150ããªç§é ããŸãïŒã ãã®çµæãã¯ã©ã€ã¢ã³ããééã£ãäºæž¬ãä¿®æ£ããå¿ èŠãããå Žåãéå»ã®ãã®æç¹ãŸã§ããŒã«ããã¯ããã®ã£ããã«å ¥åããããã¹ãŠã®æ å ±ãåçŸããŠãçŸåšã®å Žæã«æ»ãå¿ èŠããããŸãã ã²ãŒã å ã®ãã¬ã€ã€ãŒã®åããç©çåŠã«åºã¥ããŠããå Žåã1ãã¬ãŒã ã§è€æ°ã®ãµã€ã¯ã«ãã·ãã¥ã¬ãŒãããã«ã¯Physics.SimulateïŒïŒãå¿ èŠã§ãã ãã¬ã€ã€ãŒã移åãããšãã«ãã£ã©ã¯ã¿ãŒã³ã³ãããŒã©ãŒïŒãŸãã¯ã«ãã»ã«ãã£ã¹ããªã©ïŒã®ã¿ã䜿çšããå Žåã¯ãPhysics.SimulateïŒïŒã䜿çšããã«å®è¡ã§ããŸããããã©ãŒãã³ã¹ãåäžãããšæããŸãã
Unityã䜿çšããŠã Glenn Fiedlerã«ãããZen of Networked PhysicsããšåŒã°ãããããã¯ãŒã¯ãã¢ãåçŸããŸãã ãã¬ã€ã€ãŒã¯ç©ççãªç«æ¹äœãæã£ãŠãããããã«åãå ããŠã·ãŒã³ã«æŒã蟌ãããšãã§ããŸãã ãã¢ã§ã¯ãé 延ããã±ããæ倱ãªã©ãããŸããŸãªãããã¯ãŒã¯ç¶æ ãã·ãã¥ã¬ãŒãããŸãã
ä»äºãå§ãã
æåã«è¡ãããšã¯ãèªåç©çã·ãã¥ã¬ãŒã·ã§ã³ããªãã«ããããšã§ãã Physics.SimulateïŒïŒã䜿çšãããšããã€ç©çã·ã¹ãã ã«ã·ãã¥ã¬ãŒã·ã§ã³ãéå§ããããäŒããããšãã§ããŸãããããã©ã«ãã§ã¯ãåºå®ãããžã§ã¯ãæéãã«ã¿ã«åºã¥ããŠã·ãã¥ã¬ãŒã·ã§ã³ãèªåçã«å®è¡ããŸãã ãããã£ãŠã [ èªåã·ãã¥ã¬ãŒã·ã§ã³ ]ããã¯ã¹ã®ãã§ãã¯ãå€ããŠã [ç·šé]-> [ãããžã§ã¯ãèšå®]-> [ç©ç]ã§ç¡å¹ã«ããŸãã
å§ããããã«ãåçŽãªã·ã³ã°ã«ãŠãŒã¶ãŒå®è£ ãäœæããŸãã å ¥åã¯ãµã³ããªã³ã°ããïŒç§»åããå Žåã¯wãaãsãdããžã£ã³ãããå Žåã¯ã¹ããŒã¹ïŒããã¹ãŠAddForceïŒïŒã䜿çšããŠãªãžããããã£ã«é©çšãããåçŽãªåã«ãªããŸãã
public class Logic : MonoBehaviour { public GameObject player; private float timer; private void Start() { this.timer = 0.0f; } private void Update() { this.timer += Time.deltaTime; while (this.timer >= Time.fixedDeltaTime) { this.timer -= Time.fixedDeltaTime; Inputs inputs; inputs.up = Input.GetKey(KeyCode.W); inputs.down = Input.GetKey(KeyCode.S); inputs.left = Input.GetKey(KeyCode.A); inputs.right = Input.GetKey(KeyCode.D); inputs.jump = Input.GetKey(KeyCode.Space); this.AddForcesToPlayer(player.GetComponent<Rigidbody>(), inputs); Physics.Simulate(Time.fixedDeltaTime); } } }
ãããã¯ãŒã¯ã䜿çšãããŠããªããšãã«ãã¬ãŒã€ãŒã移åãã
ãµãŒããŒãžã®å ¥åã®éä¿¡
次ã«ãå ¥åããµãŒããŒã«éä¿¡ããå¿ èŠããããŸãããµãŒããŒã¯ããã®ã¢ãŒã·ã§ã³ã³ãŒããå®è¡ãããã¥ãŒãç¶æ ã®ã¹ãããã·ã§ãããäœæããŠãã¯ã©ã€ã¢ã³ãã«éãè¿ããŸãã
// client private void Update() { this.timer += Time.deltaTime; while (this.timer >= Time.fixedDeltaTime) { this.timer -= Time.fixedDeltaTime; Inputs inputs = this.SampleInputs(); InputMessage input_msg; input_msg.inputs = inputs; input_msg.tick_number = this.tick_number; this.SendToServer(input_msg); this.AddForcesToPlayer(player.GetComponent<Rigidbody>(), inputs); Physics.Simulate(Time.fixedDeltaTime); ++this.tick_number; } }
ãããŸã§ç¹å¥ãªããšã¯äœããããŸããã泚æãããã®ã¯tick_numberå€æ°ãè¿œå ããããšã ãã§ãã ãµãŒããŒããã¥ãŒãã®ç¶æ ã®ã¹ãããã·ã§ãããã¯ã©ã€ã¢ã³ãã«éä¿¡ãããšãã«ããã®ç¶æ ã«å¯Ÿå¿ããã¯ã©ã€ã¢ã³ãã®ã¿ã¯ãã確èªã§ããããã«ããå¿ èŠããããŸããããã«ããããã®ç¶æ ãäºæž¬ã¯ã©ã€ã¢ã³ããšæ¯èŒã§ããŸãïŒå°ãåŸã§è¿œå ããŸãïŒã
// server private void Update() { while (this.HasAvailableInputMessages()) { InputMessage input_msg = this.GetInputMessage(); Rigidbody rigidbody = player.GetComponent<Rigidbody>(); this.AddForcesToPlayer(rigidbody, input_msg.inputs); Physics.Simulate(Time.fixedDeltaTime); StateMessage state_msg; state_msg.position = rigidbody.position; state_msg.rotation = rigidbody.rotation; state_msg.velocity = rigidbody.velocity; state_msg.angular_velocity = rigidbody.angularVelocity; state_msg.tick_number = input_msg.tick_number + 1; this.SendToClient(state_msg); } }
ç°¡åã§ã-ãµãŒããŒã¯å ¥åã¡ãã»ãŒãžãåŸ æ©ãããããåä¿¡ãããšã¯ããã¯ãã·ãã¥ã¬ãŒãããŸãã 次ã«ãçµæã®ãã¥ãŒãã®ç¶æ ã®ã¹ãããã·ã§ãããäœæããã¯ã©ã€ã¢ã³ãã«éãè¿ããŸãã ã¹ããŒã¿ã¹ã¡ãã»ãŒãžã®tick_numberãå ¥åã¡ãã»ãŒãžã®tick_numberããã1ã€å€ãããšã«æ°ä»ããããããŸããã ããã¯ããã¿ã¯ã100ã®ãã¬ãŒã€ãŒã®ç¶æ ãããã¿ã¯ã100ã®æåã®ãã¬ãŒã€ãŒã®ç¶æ ããšèããæ¹ãå人çã«çŽæçã«äŸ¿å©ã ããã§ãã ãããã£ãŠãã¡ãžã£ãŒ100ã®ãã¬ãŒã€ãŒã®ç¶æ ãšã¡ãžã£ãŒ100ã®ãã¬ãŒã€ãŒã®å ¥åã¯ãã¡ãžã£ãŒ101ã®ãã¬ãŒã€ãŒã®æ°ããç¶æ ãäœæããŸãã
ç¶æ n +å ¥ån =ç¶æ n + 1
ç§ã¯ããªãããããåãããã«ãšãã¹ãã ãšèšã£ãŠããã®ã§ã¯ãªããäž»ãªããšã¯ã¢ãããŒãã®äžå€æ§ã§ãã
ãŸãããããã®ã¡ãã»ãŒãžãå®éã®ãœã±ãããä»ããŠéä¿¡ããã®ã§ã¯ãªãããã±ããããã¥ãŒã«æžã蟌ãã§ãã±ããã®é 延ãšæ倱ãã·ãã¥ã¬ãŒãããããšã§æš¡å£ããããšãèšããªããã°ãªããŸããã ã·ãŒã³ã«ã¯2ã€ã®ç©çãã¥ãŒããå«ãŸããŠããŸãã1ã€ã¯ã¯ã©ã€ã¢ã³ãçšã§ããã1ã€ã¯ãµãŒããŒçšã§ãã ã¯ã©ã€ã¢ã³ããã¥ãŒããæŽæ°ãããšãããµãŒããŒãã¥ãŒãã®GameObjectãç¡å¹ã«ããŸããéãåæ§ã§ãã
ãã ãããããã¯ãŒã¯ããŠã³ã¹ãšãã±ããé ä¿¡ãééã£ãé åºã§ã·ãã¥ã¬ãŒãããããšã¯ãããŸããããã®ãããåä¿¡ããåå ¥åã¡ãã»ãŒãžã¯åã®ãã®ãããæ°ãããšä»®å®ããŸãã ãã®ã·ãã¥ã¬ãŒã·ã§ã³ã¯ã1ã€ã®Unityã€ã³ã¹ã¿ã³ã¹ã§ãã¯ã©ã€ã¢ã³ãããšããµãŒããŒããéåžžã«ç°¡åã«å®è¡ãã1ã€ã®ã·ãŒã³ã§ãµãŒããŒãã¥ãŒããšã¯ã©ã€ã¢ã³ããã¥ãŒããçµã¿åãããããã«å¿ èŠã§ãã
ãŸããå ¥åã¡ãã»ãŒãžããªã»ãããããŠãµãŒããŒã«å°éããªãå ŽåããµãŒããŒã¯ã¯ã©ã€ã¢ã³ããããå°ãªãã¯ããã¯ãµã€ã¯ã«ãã·ãã¥ã¬ãŒããããããç°ãªãç¶æ ãäœæãããããšã«æ³šæããŠãã ããã ããã¯äºå®ã§ããããããã®çç¥ãã·ãã¥ã¬ãŒããããšããŠããå ¥åã¯äŸç¶ãšããŠæ£ãããªãå¯èœæ§ããããããã¯ç°ãªãç¶æ ã«ã€ãªããå¯èœæ§ããããŸãã ãã®åé¡ã¯åŸã§å¯ŸåŠããŸãã
ãŸãããã®äŸã§ã¯ã¯ã©ã€ã¢ã³ãã1ã€ãããªããããäœæ¥ãç°¡åã«ãªãããšãè¿œå ããå¿ èŠããããŸãã è€æ°ã®ã¯ã©ã€ã¢ã³ããããå ŽåãaïŒPhysics.SimulateïŒïŒãåŒã³åºããšãã«ããµãŒããŒã§1人ã®ãã¬ã€ã€ãŒã®ãã¥ãŒãã®ã¿ãæå¹ã«ãªã£ãŠããããšã確èªããããbïŒãµãŒããŒãè€æ°ã®ãã¥ãŒãããå ¥åãåãåãå Žåããããããã¹ãŠã·ãã¥ã¬ãŒãããå¿ èŠããããŸã
75 msã®é 延ïŒ150 msã®åŸåŸ©ïŒ
0ïŒ ã®çŽå€±ããã±ãŒãž
é»è²ã®ãã¥ãŒã-ãµãŒããŒãã¬ãŒã€ãŒ
éãç«æ¹äœ-ã¯ã©ã€ã¢ã³ããåãåã£ãæåŸã®ã¹ãããã·ã§ãã
ãããŸã§ã®ãšãããã¹ãŠãè¯ãããã«èŠããŸããããããªã«èšé²ãããã®ãå°ãå³éžããŠãããªãæ·±å»ãªåé¡ãé ããŸããã
決å®ã®å€±æ
ãããèŠãŠã¿ãŸãããïŒ
çã...
ãã®ãããªã¯ããã±ãŒãžã倱ãããšãªãèšé²ãããŸããããã·ãã¥ã¬ãŒã·ã§ã³ã¯ãŸã£ããåãå ¥åã§ãå€ãããŸãã ãªããããèµ·ããã®ãã¯ããããããŸãã-PhysXã¯ååã«æ±ºå®è«çã§ãªããã°ãªããªãã®ã§ãã·ãã¥ã¬ãŒã·ã§ã³ãéåžžã«é »ç¹ã«åå²ããããšã¯å°è±¡çã§ãã ããã¯ãGameObjectãã¥ãŒããåžžã«æå¹ãŸãã¯ç¡å¹ã«ããŠããããã§ããå¯èœæ§ããããŸããã€ãŸãã2ã€ã®ç°ãªãUnityã€ã³ã¹ã¿ã³ã¹ã䜿çšãããšãåé¡ãæžå°ããå¯èœæ§ããããŸãã GitHubã®ã³ãŒãã§èŠãå Žåããã°ã«ãªãå¯èœæ§ããããŸãã
ããã¯ãããããããŸããããã¯ã©ã€ã¢ã³ãåŽã§ã®äºæž¬ã§ã¯äžæ£ç¢ºãªäºæž¬ãäžå¯æ¬ ãªäºå®ãªã®ã§ããããã«å¯ŸåŠããŸãããã
å·»ãæ»ãã§ããŸããïŒ
ããã»ã¹ã¯éåžžã«ç°¡åã§ã-ã¯ã©ã€ã¢ã³ããåããäºæž¬ãããšãã圌ã¯ã¹ããŒã¿ã¹ãããã¡ïŒäœçœ®ãšå転ïŒãšå ¥åãä¿åããŸãã ãµãŒããŒããã¹ããŒã¿ã¹ã¡ãã»ãŒãžãåä¿¡ããåŸãåä¿¡ããç¶æ ããããã¡ããäºæž¬ãããç¶æ ãšæ¯èŒããŸãã å·®ã倧ããããå Žåã¯ãéå»ã®ã¯ã©ã€ã¢ã³ããã¥ãŒãã®ç¶æ ãåå®çŸ©ãããã¹ãŠã®äžéã¡ãžã£ãŒãå床ã·ãã¥ã¬ãŒãããŸãã
// client private ClientState[] client_state_buffer = new ClientState[1024]; private Inputs[] client_input_buffer = new Inputs[1024]; private void Update() { this.timer += Time.deltaTime; while (this.timer >= Time.fixedDeltaTime) { this.timer -= Time.fixedDeltaTime; Inputs inputs = this.SampleInputs(); InputMessage input_msg; input_msg.inputs = inputs; input_msg.tick_number = this.tick_number; this.SendToServer(input_msg); uint buffer_slot = this.tick_number % 1024; this.client_input_buffer[buffer_slot] = inputs; this.client_state_buffer[buffer_slot].position = rigidbody.position; this.client_state_buffer[buffer_slot].rotation = rigidbody.rotation; this.AddForcesToPlayer(player.GetComponent<Rigidbody>(), inputs); Physics.Simulate(Time.fixedDeltaTime); ++this.tick_number; } while (this.HasAvailableStateMessage()) { StateMessage state_msg = this.GetStateMessage(); uint buffer_slot = state_msg.tick_number % c_client_buffer_size; Vector3 position_error = state_msg.position - this.client_state_buffer[buffer_slot].position; if (position_error.sqrMagnitude > 0.0000001f) { // rewind & replay Rigidbody player_rigidbody = player.GetComponent<Rigidbody>(); player_rigidbody.position = state_msg.position; player_rigidbody.rotation = state_msg.rotation; player_rigidbody.velocity = state_msg.velocity; player_rigidbody.angularVelocity = state_msg.angular_velocity; uint rewind_tick_number = state_msg.tick_number; while (rewind_tick_number < this.tick_number) { buffer_slot = rewind_tick_number % c_client_buffer_size; this.client_input_buffer[buffer_slot] = inputs; this.client_state_buffer[buffer_slot].position = player_rigidbody.position; this.client_state_buffer[buffer_slot].rotation = player_rigidbody.rotation; this.AddForcesToPlayer(player_rigidbody, inputs); Physics.Simulate(Time.fixedDeltaTime); ++rewind_tick_number; } } } }
ãããã¡ãããå ¥åãšã¹ããŒã¿ã¹ã¯ãã¡ãžã£ãŒèå¥åãã€ã³ããã¯ã¹ãšããŠäœ¿çšãããéåžžã«åçŽãªåŸªç°ãããã¡ã«æ ŒçŽãããŸãã ãããŠãç©çåŠã®ã¯ããã¯åšæ³¢æ°ã«64 Hzã®å€ãéžæããŸãããã€ãŸãã1024èŠçŽ ã®ãããã¡ãŒã¯16ç§éã®ã¹ããŒã¹ãæäŸããŸãããããã¯å¿ èŠãªãã®ãããã¯ããã«å€ããªããŸãã
èšæ£äžã§ãïŒ
åé·å ¥å転é
éåžžãå ¥åã¡ãã»ãŒãžã¯éåžžã«å°ãããæŒããããã¿ã³ãçµã¿åãããŠæ°ãã€ãã®ããããã£ãŒã«ãã«ããããšãã§ããŸãã ã¡ãã»ãŒãžã«ã¯ãŸã 4ãã€ããå ããã¡ãžã£ãŒçªå·ããããŸããããã£ãªãŒä»ãã®8ãããå€ã䜿çšããŠç°¡åã«å§çž®ã§ããŸãïŒãããã0-255ã®ééã¯å°ããããã®ã§ãå®å šã«9ãŸãã¯10ãããã«å¢ããããšãã§ããŸãïŒã å Žåã«ãã£ãŠã¯ããããã®ã¡ãã»ãŒãžã¯éåžžã«å°ãããããåã¡ãã»ãŒãžã§å€§éã®å ¥åããŒã¿ãéä¿¡ã§ããŸãïŒä»¥åã®å ¥åããŒã¿ã倱ãããå ŽåïŒã ã©ã®ãããåã«æ»ããŸããïŒ ããŠãã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒããåãåã£ãæåŸã®ã¹ããŒã¿ã¹ã¡ãã»ãŒãžã®æž¬å®çªå·ãç¥ã£ãŠããã®ã§ããã®æž¬å®å€ããå ã«æ»ãããšã¯æå³ããããŸããã ãŸããã¯ã©ã€ã¢ã³ãã«ãã£ãŠéä¿¡ãããåé·ãªå ¥åããŒã¿ã®éã«å¶éã課ãå¿ èŠããããŸãã ãã¢ã§ã¯ãããè¡ããŸããã§ããããå®æããã³ãŒãã«å®è£ ããå¿ èŠããããŸãã
while (this.HasAvailableStateMessage()) { StateMessage state_msg = this.GetStateMessage(); this.client_last_received_state_tick = state_msg.tick_number;
ããã¯åçŽãªå€æŽã§ãããã¯ã©ã€ã¢ã³ãã¯æåŸã«åä¿¡ããã¹ããŒã¿ã¹ã¡ãã»ãŒãžã®æž¬å®çªå·ãæžã蟌ãã ãã§ãã
Inputs inputs = this.SampleInputs(); InputMessage input_msg; input_msg.start_tick_number = this.client_last_received_state_tick; input_msg.inputs = new List<Inputs>(); for (uint tick = this.client_last_received_state_tick; tick <= this.tick_number; ++tick) { input_msg.inputs.Add(this.client_input_buffer[tick % 1024]); } this.SendToServer(input_msg);
ã¯ã©ã€ã¢ã³ãããéä¿¡ãããå ¥åã¡ãã»ãŒãžã«ã¯ã1ã€ã®ã¢ã€ãã ã ãã§ãªããå ¥åããŒã¿ã®ãªã¹ããå«ãŸããããã«ãªããŸããã å°ç¯çªå·ã®ããéšåã¯æ°ããå€ãååŸããŸã-ããããã®ãªã¹ãã®æåã®å ¥åã®å°ç¯çªå·ã§ãã
while (this.HasAvailableInputMessages()) { InputMessage input_msg = this.GetInputMessage(); // message contains an array of inputs, calculate what tick the final one is uint max_tick = input_msg.start_tick_number + (uint)input_msg.inputs.Count - 1; // if that tick is greater than or equal to the current tick we're on, then it // has inputs which are new if (max_tick >= server_tick_number) { // there may be some inputs in the array that we've already had, // so figure out where to start uint start_i = server_tick_number > input_msg.start_tick_number ? (server_tick_number - input_msg.start_tick_number) : 0; // run through all relevant inputs, and step player forward Rigidbody rigidbody = player.GetComponent<Rigidbody>(); for (int i = (int)start_i; i < input_msg.inputs.Count; ++i) { this.AddForcesToPlayer(rigidbody, input_msg.inputs[i]); Physics.Simulate(Time.fixedDeltaTime); } server_tick_number = max_tick + 1; } }
ãµãŒããŒã¯ãå ¥åã¡ãã»ãŒãžãåä¿¡ãããšãæåã®å ¥åã®æž¬å®çªå·ãšã¡ãã»ãŒãžå ã®å ¥åããŒã¿ã®éãèªèããŸãã ãããã£ãŠãã¡ãã»ãŒãžã®æåŸã®å ¥åã®ããŒããèšç®ã§ããŸãã ãã®æåŸã®æž¬å®å€ããµãŒããŒæž¬å®å€ä»¥äžã§ããå ŽåããµãŒããŒããŸã èŠãŠããªãå ¥åãå°ãªããšã1ã€å«ãŸããŠããããšãããããŸãã ãããããªããããã¯ãã¹ãŠã®æ°ããå ¥åããŒã¿ãã·ãã¥ã¬ãŒãããŸãã
å ¥åã¡ãã»ãŒãžã®åé·ãªå ¥åããŒã¿ã®éãå¶éãããšãååãªæ°ã®å ¥åã¡ãã»ãŒãžã倱ããããšããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®éã«ã·ãã¥ã¬ãŒã·ã§ã³ã®ã®ã£ãããçããããšã«æ°ã¥ãããããããŸããã ã€ãŸãããµãŒããŒã¯ã¡ãžã£ãŒ100ãã·ãã¥ã¬ãŒãããã¹ããŒã¿ã¹ã¡ãã»ãŒãžãéä¿¡ããŠã¡ãžã£ãŒ101ãéå§ããŠãããã¡ãžã£ãŒ105ããå§ãŸãå ¥åã¡ãã»ãŒãžãåä¿¡ã§ããŸããäžèšã®ã³ãŒãã§ã¯ããµãŒããŒã¯105ã«é²ã¿ãææ°ã®æ¢ç¥ã®å ¥åããŒã¿ã«åºã¥ããŠäžéã¡ãžã£ãŒãã·ãã¥ã¬ãŒãããŸããã ããªãããããå¿ èŠãšãããã©ããã¯ããªãã®æ±ºå®ãšã²ãŒã ãã©ãããã¹ããã«äŸåããŸãã å人çã«ã¯ããããã¯ãŒã¯ã®ç¶æ ãæªãããããµãŒããŒããããäžã§ãã¬ãŒã€ãŒãæšæž¬ããŠç§»åããããšã匷å¶ããŸããã æ¥ç¶ãå埩ãããŸã§ããã¬ãŒã€ãŒããã®ãŸãŸã«ããŠããæ¹ãè¯ããšèããŠããŸãã
Zen of Networked Physicsãã¢ã«ã¯ãã¯ã©ã€ã¢ã³ãã«ãããéèŠãªåãããéä¿¡ããæ©èœããããŸããã€ãŸãã以åã«éä¿¡ãããå ¥åãšç°ãªãå Žåã«ã®ã¿ãåé·ãªå ¥åããŒã¿ãéä¿¡ããŸãã ããã¯å ¥åãã«ã¿å§çž®ãšåŒã°ããããã䜿çšããŠå ¥åã¡ãã»ãŒãžã®ãµã€ãºãããã«åæžã§ããŸãã ãã ãããã®ãã¢ã§ã¯ãããã¯ãŒã¯è² è·ã®æé©åããªãããããããŸã§ã®ãšãããããè¡ã£ãŠããŸããã
åé·ãªå ¥åããŒã¿ãéä¿¡ããåïŒãã±ããã®25ïŒ ã倱ãããå Žåããã¥ãŒãã®åãã¯é ããŠçæ£ããåŒãç¶ãã¹ããŒããã¯ãããŸãã
åé·ãªå ¥åããŒã¿ãéä¿¡ããåŸïŒãã±ããã®25ïŒ ã倱ãããå Žåã§ããè£æ£ã¯åŒãããããŸããããã¥ãŒãã¯èš±å®¹å¯èœãªé床ã§ç§»åããŸãã
å¯å€ã¹ãããã·ã§ããé »åºŠ
ãã®ãã¢ã§ã¯ããµãŒããŒãã¯ã©ã€ã¢ã³ãã«ã¹ãããã·ã§ãããéä¿¡ããé »åºŠãç°ãªããŸãã é »åºŠãæžãããšãã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒããä¿®æ£ãåä¿¡ããã®ã«ããå€ãã®æéãå¿ èŠã«ãªããŸãã ãããã£ãŠãã¯ã©ã€ã¢ã³ããäºæž¬ãééããå Žåãã¹ããŒã¿ã¹ã¡ãã»ãŒãžãåä¿¡ããåã«ãã¯ã©ã€ã¢ã³ãã¯ããã«éžè±ããå¯èœæ§ããããããã«ãããããé¡èãªä¿®æ£ãããããããŸãã ã¹ãããã·ã§ããã®é »åºŠãé«ãå Žåããã±ããæ倱ã¯ããã»ã©éèŠã§ã¯ãªããããã¯ã©ã€ã¢ã³ãã¯æ¬¡ã®ã¹ãããã·ã§ãããåä¿¡ããããã«é·ãåŸ ã€å¿ èŠã¯ãããŸããã
ã¹ãããã·ã§ããåšæ³¢æ°64 Hz
ã¹ãããã·ã§ããåšæ³¢æ°16 Hz
ã¹ãããã·ã§ããåšæ³¢æ°2 Hz
æããã«ãã¹ãããã·ã§ããã®é »åºŠã¯é«ãã»ã©è¯ãã®ã§ãã§ããã ãé »ç¹ã«éä¿¡ããå¿ èŠããããŸãã ãã ããè¿œå ã®ãã©ãã£ãã¯ã®éããã®ã³ã¹ããå°çšãµãŒããŒã®å¯çšæ§ããµãŒããŒã®ã³ã³ãã¥ãŒãã£ã³ã°ã³ã¹ããªã©ã«äŸåããŸãã
å¹³æ»åè£æ£
äžæ£ç¢ºãªäºæž¬ãäœæããå¿ èŠä»¥äžã«ãããããããä¿®æ£ãååŸããŸãã Unity / PhysXçµ±åãžã®é©åãªã¢ã¯ã»ã¹ãªãã§ã¯ããããã®èª€ã£ãäºæž¬ããããã°ããããšã¯ã»ãšãã©ã§ããŸããã ç§ã¯ãããåã«èšã£ãããç§ã¯åã³ç¹°ãè¿ã-ããªããç§ãééã£ãŠããç©çåŠã«é¢é£ããäœããèŠã€ããããããã«ã€ããŠç§ã«ç¥ãããŠãã ããã
å€ãè¯ãã¹ã ãŒãžã³ã°ã§äºè£ãå æ²¢åããããšã§ããã®åé¡ã®è§£æ±ºçãåé¿ããŸããïŒ ä¿®æ£ãè¡ããããšãã¯ã©ã€ã¢ã³ãã¯ãã¬ãŒã€ãŒã®äœçœ®ãšå転ãããã€ãã®ãã¬ãŒã ã®æ£ããç¶æ ã®æ¹åã«åçŽã«æ»ããã«ããŸãã ç©çãã¥ãŒãèªäœã¯å³åº§ã«ä¿®æ£ãããŸãïŒé衚瀺ïŒãã衚瀺å°çšã®2çªç®ã®ãã¥ãŒãããããã¹ã ãŒãžã³ã°ãå¯èœã§ãã
Vector3 position_error = state_msg.position - predicted_state.position; float rotation_error = 1.f - Quaternion.Dot(state_msg.rotation, predicted_state.rotation); if (position_error.sqrMagnitude > 0.0000001f || rotation_error > 0.00001f) { Rigidbody player_rigidbody = player.GetComponent<Rigidbody>(); // capture the current predicted pos for smoothing Vector3 prev_pos = player_rigidbody.position + this.client_pos_error; Quaternion prev_rot = player_rigidbody.rotation * this.client_rot_error; // rewind & replay player_rigidbody.position = state_msg.position; player_rigidbody.rotation = state_msg.rotation; player_rigidbody.velocity = state_msg.velocity; player_rigidbody.angularVelocity = state_msg.angular_velocity; uint rewind_tick_number = state_msg.tick_number; while (rewind_tick_number < this.tick_number) { buffer_slot = rewind_tick_number % c_client_buffer_size; this.client_input_buffer[buffer_slot] = inputs; this.client_state_buffer[buffer_slot].position = player_rigidbody.position; this.client_state_buffer[buffer_slot].rotation = player_rigidbody.rotation; this.AddForcesToPlayer(player_rigidbody, inputs); Physics.Simulate(Time.fixedDeltaTime); ++rewind_tick_number; } // if more than 2ms apart, just snap if ((prev_pos - player_rigidbody.position).sqrMagnitude >= 4.0f) { this.client_pos_error = Vector3.zero; this.client_rot_error = Quaternion.identity; } else { this.client_pos_error = prev_pos - player_rigidbody.position; this.client_rot_error = Quaternion.Inverse(player_rigidbody.rotation) * prev_rot; } }
誀ã£ãäºæž¬ãçºçãããšãã¯ã©ã€ã¢ã³ãã¯ä¿®æ£åŸã®äœçœ®/å転差ãç£èŠããŸãã äœçœ®è£æ£ã®åèšè·é¢ã2ã¡ãŒãã«ãè¶ ããå Žåãç«æ¹äœã¯åçŽã«åããŸã-ã¹ã ãŒãžã³ã°ã¯ââäŸç¶ãšããŠæªãèŠããã®ã§ãå°ãªããšãã§ããã ãæ©ãæ£ããç¶æ ã«æ»ããŸãã
this.client_pos_error *= 0.9f; this.client_rot_error = Quaternion.Slerp(this.client_rot_error, Quaternion.identity, 0.1f); this.smoothed_client_player.transform.position = player_rigidbody.position + this.client_pos_error; this.smoothed_client_player.transform.rotation = player_rigidbody.rotation * this.client_rot_error;
åãã¬ãŒã ã§ãã¯ã©ã€ã¢ã³ãã¯lerp / slerpãæ£ããäœçœ®/å転ã«åããŠ10ïŒ å®è¡ããŸããããã¯ãåããå¹³ååããããã®æšæºã®ã¹ãä¹åã¢ãããŒãã§ãã ãã¬ãŒã ã¬ãŒãã«ãã£ãŠç°ãªããŸããããã¢ã®ç®çã«ã¯ããã§ååã§ãã
250ããªç§ã®é 延
ããã±ãŒãžã®10ïŒ ã倱ã£ã
ã¹ã ãŒãžã³ã°ãªãã§ã¯ãè£æ£ã¯éåžžã«é¡èã§ã
250ããªç§ã®é 延
ããã±ãŒãžã®10ïŒ ã倱ã£ã
ã¹ã ãŒãžã³ã°ã䜿çšãããšãä¿®æ£ãèªèãã«ãããªããŸãã
æçµçµæã¯ããªãããŸããããŸãããã±ãããæš¡å£ããã®ã§ã¯ãªããå®éã«ãã±ãããéä¿¡ããããŒãžã§ã³ãäœæããããšæããŸãã ããããå°ãªããšãããã¯ãç©ççãªãã©ã°ã€ã³ãªã©ãå¿ èŠãšããã«Unityã§å®éã®ç©çãªããžã§ã¯ãã䜿çšããã¯ã©ã€ã¢ã³ãåŽäºæž¬ã·ã¹ãã ã®æŠå¿µå®èšŒã§ãã