నెట్‌వర్క్ లేటెన్సీ పరిహారం అల్గారిథమ్‌తో మొబైల్ షూటర్ కోసం మేము బాలిస్టిక్ లెక్కల మెకానిక్స్‌ను ఎలా మెరుగుపరిచాము

నెట్‌వర్క్ లేటెన్సీ పరిహారం అల్గారిథమ్‌తో మొబైల్ షూటర్ కోసం మేము బాలిస్టిక్ లెక్కల మెకానిక్స్‌ను ఎలా మెరుగుపరిచాము

హాయ్, నేను నికితా బ్రిజాక్, పిక్సోనిక్ నుండి సర్వర్ డెవలపర్. ఈ రోజు నేను మొబైల్ మల్టీప్లేయర్‌లో లాగ్‌ను భర్తీ చేయడం గురించి మాట్లాడాలనుకుంటున్నాను.

రష్యన్ భాషతో సహా సర్వర్ లాగ్ పరిహారం గురించి చాలా కథనాలు వ్రాయబడ్డాయి. ఇది ఆశ్చర్యం కలిగించదు, ఎందుకంటే ఈ సాంకేతికత 90 ల చివరి నుండి మల్టీప్లేయర్ FPS యొక్క సృష్టిలో చురుకుగా ఉపయోగించబడింది. ఉదాహరణకు, మీరు QuakeWorld మోడ్‌ను గుర్తుంచుకోవచ్చు, ఇది మొదట ఉపయోగించిన వాటిలో ఒకటి.

మేము దీన్ని మా మొబైల్ మల్టీప్లేయర్ షూటర్ డినో స్క్వాడ్‌లో కూడా ఉపయోగిస్తాము.

ఈ కథనంలో, నా లక్ష్యం ఇప్పటికే వెయ్యి సార్లు వ్రాసిన వాటిని పునరావృతం చేయడం కాదు, కానీ మా సాంకేతికత స్టాక్ మరియు కోర్ గేమ్‌ప్లే లక్షణాలను పరిగణనలోకి తీసుకొని మా గేమ్‌లో లాగ్ పరిహారాన్ని ఎలా అమలు చేసాము అని చెప్పడం.

మా కార్టెక్స్ మరియు టెక్నాలజీ గురించి కొన్ని మాటలు.

డినో స్క్వాడ్ ఒక నెట్‌వర్క్ మొబైల్ PvP షూటర్. ఆటగాళ్ళు వివిధ రకాల ఆయుధాలను కలిగి ఉన్న డైనోసార్లను నియంత్రిస్తారు మరియు 6v6 జట్లలో ఒకరితో ఒకరు పోరాడుతారు.

క్లయింట్ మరియు సర్వర్ రెండూ యూనిటీపై ఆధారపడి ఉంటాయి. షూటర్‌లకు ఆర్కిటెక్చర్ చాలా క్లాసిక్: సర్వర్ నిరంకుశమైనది మరియు క్లయింట్ అంచనా క్లయింట్‌లపై పనిచేస్తుంది. గేమ్ సిమ్యులేషన్ అంతర్గత ECS ఉపయోగించి వ్రాయబడింది మరియు సర్వర్ మరియు క్లయింట్ రెండింటిలోనూ ఉపయోగించబడుతుంది.

మీరు లాగ్ పరిహారం గురించి వినడం ఇదే మొదటిసారి అయితే, సమస్య గురించి క్లుప్త విహారం ఇక్కడ ఉంది.

మల్టీప్లేయర్ FPS గేమ్‌లలో, మ్యాచ్ సాధారణంగా రిమోట్ సర్వర్‌లో అనుకరించబడుతుంది. ప్లేయర్‌లు తమ ఇన్‌పుట్‌ను (నొక్కిన కీల గురించిన సమాచారం) సర్వర్‌కు పంపుతారు మరియు ప్రతిస్పందనగా సర్వర్ అందుకున్న డేటాను పరిగణనలోకి తీసుకుని వారికి నవీకరించబడిన గేమ్ స్థితిని పంపుతుంది. ఈ ఇంటరాక్షన్ స్కీమ్‌తో, ఫార్వర్డ్ కీని నొక్కడం మరియు ప్లేయర్ క్యారెక్టర్ స్క్రీన్‌పై కదులుతున్న క్షణం మధ్య ఆలస్యం ఎల్లప్పుడూ పింగ్ కంటే ఎక్కువగా ఉంటుంది.

స్థానిక నెట్‌వర్క్‌లలో ఈ ఆలస్యం (ప్రసిద్ధంగా ఇన్‌పుట్ లాగ్ అని పిలుస్తారు) గుర్తించబడకపోవచ్చు, ఇంటర్నెట్ ద్వారా ప్లే చేస్తున్నప్పుడు అది పాత్రను నియంత్రించేటప్పుడు "మంచుపై జారడం" అనుభూతిని కలిగిస్తుంది. మొబైల్ నెట్‌వర్క్‌లకు ఈ సమస్య రెండింతలు సంబంధితంగా ఉంటుంది, ఇక్కడ ప్లేయర్ పింగ్ 200 ఎంఎస్‌లు ఉన్న సందర్భం ఇప్పటికీ అద్భుతమైన కనెక్షన్‌గా పరిగణించబడుతుంది. తరచుగా పింగ్ 350, 500 లేదా 1000 ms ఉంటుంది. అప్పుడు ఇన్‌పుట్ లాగ్‌తో ఫాస్ట్ షూటర్‌ను ప్లే చేయడం దాదాపు అసాధ్యం అవుతుంది.

ఈ సమస్యకు పరిష్కారం క్లయింట్ వైపు అనుకరణ అంచనా. ఇక్కడ క్లయింట్ సర్వర్ నుండి ప్రతిస్పందన కోసం వేచి ఉండకుండా ప్లేయర్ క్యారెక్టర్‌కు ఇన్‌పుట్‌ను వర్తింపజేస్తుంది. మరియు సమాధానం వచ్చినప్పుడు, ఇది ఫలితాలను సరిపోల్చడం మరియు ప్రత్యర్థుల స్థానాలను నవీకరిస్తుంది. ఈ సందర్భంలో కీని నొక్కడం మరియు ఫలితాన్ని స్క్రీన్‌పై ప్రదర్శించడం మధ్య ఆలస్యం తక్కువగా ఉంటుంది.

ఇక్కడ స్వల్పభేదాన్ని అర్థం చేసుకోవడం చాలా ముఖ్యం: క్లయింట్ ఎల్లప్పుడూ దాని చివరి ఇన్‌పుట్ ప్రకారం తనను తాను ఆకర్షిస్తుంది మరియు శత్రువులు - నెట్‌వర్క్ ఆలస్యంతో, సర్వర్ నుండి డేటా నుండి మునుపటి స్థితి ప్రకారం. అంటే, శత్రువుపై కాల్పులు జరుపుతున్నప్పుడు, ఆటగాడు అతనిని తనకు సంబంధించి గతంలో చూస్తాడు. క్లయింట్ అంచనా గురించి మరింత మేము ఇంతకు ముందు వ్రాసాము.

ఈ విధంగా, క్లయింట్ అంచనా ఒక సమస్యను పరిష్కరిస్తుంది, కానీ మరొక సమస్యను సృష్టిస్తుంది: ప్లేయర్ గతంలో శత్రువు ఉన్న ప్రదేశంలో షూట్ చేస్తే, అదే సమయంలో షూట్ చేస్తున్నప్పుడు సర్వర్‌పై, శత్రువు ఇకపై ఆ స్థలంలో ఉండకపోవచ్చు. సర్వర్ లాగ్ పరిహారం ఈ సమస్యను పరిష్కరించడానికి ప్రయత్నిస్తుంది. ఆయుధాన్ని కాల్చినప్పుడు, షాట్ సమయంలో ప్లేయర్ స్థానికంగా చూసిన గేమ్ స్థితిని సర్వర్ పునరుద్ధరిస్తుంది మరియు అతను నిజంగా శత్రువును కొట్టగలడా అని తనిఖీ చేస్తుంది. సమాధానం "అవును" అయితే, ఆ సమయంలో శత్రువు సర్వర్‌లో లేనప్పటికీ, హిట్ లెక్కించబడుతుంది.

ఈ పరిజ్ఞానంతో, మేము డినో స్క్వాడ్‌లో సర్వర్ లాగ్ పరిహారాన్ని అమలు చేయడం ప్రారంభించాము. అన్నింటిలో మొదటిది, క్లయింట్ చూసిన దాన్ని సర్వర్‌లో ఎలా పునరుద్ధరించాలో మనం అర్థం చేసుకోవాలి? మరియు సరిగ్గా పునరుద్ధరించాల్సిన అవసరం ఏమిటి? మా గేమ్‌లో, ఆయుధాలు మరియు సామర్థ్యాల నుండి వచ్చే హిట్‌లు రేకాస్ట్‌లు మరియు ఓవర్‌లేల ద్వారా గణించబడతాయి - అంటే శత్రువు యొక్క భౌతిక కొలైడర్‌లతో పరస్పర చర్యల ద్వారా. దీని ప్రకారం, ప్లేయర్ స్థానికంగా "చూసిన" ఈ కొలైడర్‌ల స్థానాన్ని మేము సర్వర్‌లో పునరుత్పత్తి చేయాలి. ఆ సమయంలో మేము యూనిటీ వెర్షన్ 2018.xని ఉపయోగిస్తున్నాము. భౌతిక API స్థిరంగా ఉంటుంది, భౌతిక ప్రపంచం ఒకే కాపీలో ఉంది. దాని స్థితిని సేవ్ చేసి, బాక్స్ నుండి దాన్ని పునరుద్ధరించడానికి మార్గం లేదు. కాబట్టి ఏమి చేయాలి?

పరిష్కారం ఉపరితలంపై ఉంది; ఇతర సమస్యలను పరిష్కరించడానికి దాని అన్ని అంశాలు ఇప్పటికే ఉపయోగించబడ్డాయి:

  1. ప్రతి క్లయింట్ కోసం, అతను కీలను నొక్కినప్పుడు ప్రత్యర్థులను ఏ సమయంలో చూశాడో మనం తెలుసుకోవాలి. మేము ఇప్పటికే ఈ సమాచారాన్ని ఇన్‌పుట్ ప్యాకేజీలో వ్రాసి, క్లయింట్ అంచనాను సర్దుబాటు చేయడానికి ఉపయోగించాము.
  2. మేము గేమ్ రాష్ట్రాల చరిత్రను నిల్వ చేయగలగాలి. అందులోనే మనం మన ప్రత్యర్థుల (అందువలన వారి కొలైడర్లు) స్థానాలను కలిగి ఉంటాము. మేము ఇప్పటికే సర్వర్‌లో రాష్ట్ర చరిత్రను కలిగి ఉన్నాము, మేము దానిని నిర్మించడానికి ఉపయోగించాము డెల్టాలు. సరైన సమయాన్ని తెలుసుకుంటే, చరిత్రలో సరైన స్థితిని మనం సులభంగా కనుగొనవచ్చు.
  3. ఇప్పుడు మేము చరిత్ర నుండి గేమ్ స్థితిని కలిగి ఉన్నాము, మేము ప్లేయర్ డేటాను భౌతిక ప్రపంచం యొక్క స్థితితో సమకాలీకరించగలగాలి. ఇప్పటికే ఉన్న కొలైడర్లు - తరలించు, తప్పిపోయినవి - సృష్టించు, అనవసరమైనవి - నాశనం. ఈ తర్కం ఇప్పటికే వ్రాయబడింది మరియు అనేక ECS వ్యవస్థలను కలిగి ఉంది. ఒక యూనిటీ ప్రాసెస్‌లో అనేక గేమ్ రూమ్‌లను ఉంచడానికి మేము దీనిని ఉపయోగించాము. మరియు భౌతిక ప్రపంచం ప్రక్రియకు ఒకటి కాబట్టి, దానిని గదుల మధ్య తిరిగి ఉపయోగించాల్సి వచ్చింది. అనుకరణ యొక్క ప్రతి టిక్కు ముందు, మేము భౌతిక ప్రపంచం యొక్క స్థితిని "రీసెట్" చేసాము మరియు ప్రస్తుత గదికి సంబంధించిన డేటాతో దానిని పునఃప్రారంభించాము, ఒక తెలివైన పూలింగ్ సిస్టమ్ ద్వారా సాధ్యమైనంత ఎక్కువ యూనిటీ గేమ్ వస్తువులను తిరిగి ఉపయోగించేందుకు ప్రయత్నిస్తున్నాము. గతం నుండి గేమ్ స్థితి కోసం అదే లాజిక్‌ను ప్రారంభించడం మాత్రమే మిగిలి ఉంది.

ఈ అంశాలన్నింటినీ కలిపి ఉంచడం ద్వారా, భౌతిక ప్రపంచం యొక్క స్థితిని సరైన క్షణానికి వెనక్కి తిప్పగలిగే “టైమ్ మెషిన్” మాకు లభించింది. కోడ్ సరళమైనదిగా మారింది:

public class TimeMachine : ITimeMachine
{
     //История игровых состояний
     private readonly IGameStateHistory _history;

     //Текущее игровое состояние на сервере
     private readonly ExecutableSystem[] _systems;

     //Набор систем, расставляющих коллайдеры в физическом мире 
     //по данным из игрового состояния
     private readonly GameState _presentState;

     public TimeMachine(IGameStateHistory history, GameState presentState, ExecutableSystem[] timeInitSystems)
     {
         _history = history; 
         _presentState = presentState;
         _systems = timeInitSystems;  
     }

     public GameState TravelToTime(int tick)
     {
         var pastState = tick == _presentState.Time ? _presentState : _history.Get(tick);
         foreach (var system in _systems)
         {
             system.Execute(pastState);
         }
         return pastState;
     }
}

షాట్లు మరియు సామర్థ్యాలను సులభంగా భర్తీ చేయడానికి ఈ యంత్రాన్ని ఎలా ఉపయోగించాలో గుర్తించడం మాత్రమే మిగిలి ఉంది.

సరళమైన సందర్భంలో, మెకానిక్స్ ఒకే హిట్‌స్కాన్‌పై ఆధారపడి ఉన్నప్పుడు, ప్రతిదీ స్పష్టంగా ఉన్నట్లు అనిపిస్తుంది: ఆటగాడు షూట్ చేయడానికి ముందు, అతను భౌతిక ప్రపంచాన్ని కావలసిన స్థితికి తిప్పికొట్టాలి, రేకాస్ట్ చేయాలి, హిట్ లేదా మిస్‌ని లెక్కించాలి మరియు ప్రపంచాన్ని ప్రారంభ స్థితికి తిరిగి ఇవ్వండి.

కానీ డినో స్క్వాడ్‌లో అలాంటి మెకానిక్‌లు చాలా తక్కువ! గేమ్‌లోని చాలా ఆయుధాలు ప్రక్షేపకాలను సృష్టిస్తాయి - దీర్ఘకాల బుల్లెట్‌లు అనేక అనుకరణ పేలు (కొన్ని సందర్భాల్లో, డజన్ల కొద్దీ పేలు) కోసం ఎగురుతాయి. వాటితో ఏం చేయాలి, ఏ సమయంలో ఎగరాలి?

В పురాతన వ్యాసం హాఫ్-లైఫ్ నెట్‌వర్క్ స్టాక్ గురించి, వాల్వ్‌లోని కుర్రాళ్ళు అదే ప్రశ్న అడిగారు మరియు వారి సమాధానం ఇది: ప్రక్షేపకం లాగ్ పరిహారం సమస్యాత్మకమైనది మరియు దానిని నివారించడం మంచిది.

మాకు ఈ ఎంపిక లేదు: గేమ్ డిజైన్‌లో ప్రక్షేపకం ఆధారిత ఆయుధాలు ఒక ముఖ్య లక్షణం. కాబట్టి మేము ఏదో ఒకదానితో ముందుకు రావాలి. కొంత మేధోమథనం తర్వాత, మేము పని చేసినట్లు అనిపించే రెండు ఎంపికలను రూపొందించాము:

1. మేము దానిని సృష్టించిన ఆటగాడి సమయానికి ప్రక్షేపకాన్ని కట్టివేస్తాము. సర్వర్ అనుకరణ యొక్క ప్రతి టిక్, ప్రతి ప్లేయర్ యొక్క ప్రతి బుల్లెట్ కోసం, మేము భౌతిక ప్రపంచాన్ని క్లయింట్ స్థితికి వెనక్కి తీసుకుంటాము మరియు అవసరమైన గణనలను చేస్తాము. ఈ విధానం సర్వర్‌పై పంపిణీ చేయబడిన లోడ్ మరియు ప్రక్షేపకాల యొక్క ఊహాజనిత విమాన సమయాన్ని కలిగి ఉండటం సాధ్యపడింది. క్లయింట్‌పై శత్రు ప్రక్షేపకాలతో సహా అన్ని ప్రక్షేపకాలను కలిగి ఉన్నందున, అంచనా వేయడం మాకు చాలా ముఖ్యమైనది.

నెట్‌వర్క్ లేటెన్సీ పరిహారం అల్గారిథమ్‌తో మొబైల్ షూటర్ కోసం మేము బాలిస్టిక్ లెక్కల మెకానిక్స్‌ను ఎలా మెరుగుపరిచాము
చిత్రంలో, టిక్ 30 వద్ద ఉన్న ఆటగాడు ఊహించి క్షిపణిని కాల్చాడు: అతను శత్రువు ఏ దిశలో పరిగెడుతున్నాడో చూస్తాడు మరియు క్షిపణి యొక్క సుమారు వేగాన్ని తెలుసుకుంటాడు. స్థానికంగా అతను 33వ టిక్ వద్ద లక్ష్యాన్ని చేధించినట్లు చూస్తాడు. లాగ్ పరిహారం కారణంగా, ఇది సర్వర్‌లో కూడా కనిపిస్తుంది

2. మేము మొదటి ఎంపికలో వలె ప్రతిదీ చేస్తాము, కానీ, బుల్లెట్ అనుకరణ యొక్క ఒక టిక్‌ను లెక్కించిన తర్వాత, మేము ఆపము, కానీ అదే సర్వర్ టిక్‌లో దాని విమానాన్ని అనుకరించడం కొనసాగిస్తాము, ప్రతిసారీ దాని సమయాన్ని సర్వర్‌కు దగ్గరగా తీసుకువస్తుంది. ఒక్కొక్కటిగా టిక్ చేసి కొలైడర్ స్థానాలను నవీకరిస్తోంది. రెండు విషయాలలో ఒకటి జరిగే వరకు మేము దీన్ని చేస్తాము:

  • బుల్లెట్ గడువు ముగిసింది. అంటే లెక్కలు ముగిశాయని, మిస్ లేదా హిట్ అని లెక్కించవచ్చు. మరియు ఇది షాట్ కాల్చబడిన అదే టిక్ వద్ద ఉంది! మాకు ఇది ప్లస్ మరియు మైనస్ రెండూ. ఒక ప్లస్ - ఎందుకంటే షూటింగ్ ప్లేయర్‌కి ఇది హిట్ మరియు శత్రువు ఆరోగ్యంలో తగ్గుదల మధ్య ఆలస్యాన్ని గణనీయంగా తగ్గించింది. ప్రతికూలత ఏమిటంటే, ప్రత్యర్థులు ఆటగాడిపై కాల్పులు జరిపినప్పుడు అదే ప్రభావం గమనించబడింది: శత్రువు, నెమ్మదిగా రాకెట్‌ను మాత్రమే కాల్చినట్లు అనిపిస్తుంది మరియు నష్టం ఇప్పటికే లెక్కించబడింది.
  • బుల్లెట్ సర్వర్ సమయానికి చేరుకుంది. ఈ సందర్భంలో, దాని అనుకరణ ఎటువంటి లాగ్ పరిహారం లేకుండా తదుపరి సర్వర్ టిక్‌లో కొనసాగుతుంది. నెమ్మదైన ప్రక్షేపకాల కోసం, ఇది మొదటి ఎంపికతో పోలిస్తే భౌతిక శాస్త్ర రీల్‌బ్యాక్‌ల సంఖ్యను సిద్ధాంతపరంగా తగ్గించగలదు. అదే సమయంలో, సిమ్యులేషన్‌పై అసమాన లోడ్ పెరిగింది: సర్వర్ నిష్క్రియంగా ఉంది లేదా ఒక సర్వర్ టిక్‌లో అనేక బుల్లెట్‌ల కోసం డజను సిమ్యులేషన్ టిక్‌లను లెక్కిస్తోంది.

నెట్‌వర్క్ లేటెన్సీ పరిహారం అల్గారిథమ్‌తో మొబైల్ షూటర్ కోసం మేము బాలిస్టిక్ లెక్కల మెకానిక్స్‌ను ఎలా మెరుగుపరిచాము
మునుపటి చిత్రంలో ఉన్న అదే దృశ్యం, కానీ రెండవ పథకం ప్రకారం లెక్కించబడుతుంది. షాట్ సంభవించిన అదే టిక్ వద్ద సర్వర్ సమయంతో క్షిపణి "క్యాచ్ అప్" చేయబడింది మరియు హిట్‌ని తదుపరి టిక్‌కు ముందుగానే లెక్కించవచ్చు. 31వ టిక్ వద్ద, ఈ సందర్భంలో, లాగ్ పరిహారం ఇకపై వర్తించదు

మా అమలులో, ఈ రెండు విధానాలు కేవలం రెండు పంక్తుల కోడ్‌లో విభిన్నంగా ఉన్నాయి, కాబట్టి మేము రెండింటినీ సృష్టించాము మరియు చాలా కాలం పాటు అవి సమాంతరంగా ఉన్నాయి. ఆయుధం యొక్క మెకానిక్స్ మరియు బుల్లెట్ యొక్క వేగాన్ని బట్టి, మేము ప్రతి డైనోసార్ కోసం ఒకటి లేదా మరొక ఎంపికను ఎంచుకున్నాము. మెకానిక్‌ల ఆటలో "ఇలాంటి సమయంలో మీరు శత్రువును చాలాసార్లు కొట్టినట్లయితే, అలాంటి మరియు అలాంటి బోనస్ పొందండి" వంటి మెకానిక్స్ ఆటలో కనిపించడం ఇక్కడ మలుపు. ఆటగాడు శత్రువును కొట్టిన సమయంలో ముఖ్యమైన పాత్ర పోషించిన ఏదైనా మెకానిక్ రెండవ విధానంతో పనిచేయడానికి నిరాకరించాడు. కాబట్టి మేము మొదటి ఎంపికతో వెళ్లడం ముగించాము మరియు ఇది ఇప్పుడు ఆటలోని అన్ని ఆయుధాలు మరియు అన్ని క్రియాశీల సామర్థ్యాలకు వర్తిస్తుంది.

విడిగా, పనితీరు సమస్యను పెంచడం విలువ. ఇవన్నీ నెమ్మదిగా పని చేస్తాయని మీరు అనుకుంటే, నేను సమాధానం ఇస్తాను: ఇది. కొలైడర్‌లను తరలించడంలో మరియు వాటిని ఆన్ మరియు ఆఫ్ చేయడంలో ఐక్యత చాలా నెమ్మదిగా ఉంటుంది. డినో స్క్వాడ్‌లో, "చెత్త" సందర్భంలో, పోరాటంలో ఏకకాలంలో అనేక వందల ప్రక్షేపకాలు ఉండవచ్చు. ప్రతి ప్రక్షేపకాన్ని ఒక్కొక్కటిగా లెక్కించడానికి కొలైడర్‌లను తరలించడం భరించలేని లగ్జరీ. అందువల్ల, భౌతిక "రోల్‌బ్యాక్‌ల" సంఖ్యను తగ్గించడం మాకు ఖచ్చితంగా అవసరం. దీన్ని చేయడానికి, మేము ECSలో ఒక ప్రత్యేక భాగాన్ని సృష్టించాము, దీనిలో మేము ప్లేయర్ సమయాన్ని రికార్డ్ చేస్తాము. మేము లాగ్ పరిహారం అవసరమయ్యే అన్ని ఎంటిటీలకు (ప్రాజెక్టైల్‌లు, సామర్థ్యాలు మొదలైనవి) జోడించాము. మేము అటువంటి ఎంటిటీలను ప్రాసెస్ చేయడం ప్రారంభించే ముందు, మేము ఈ సమయానికి వాటిని క్లస్టర్ చేస్తాము మరియు వాటిని కలిసి ప్రాసెస్ చేస్తాము, ప్రతి క్లస్టర్‌కు ఒకసారి భౌతిక ప్రపంచాన్ని వెనక్కి తిప్పుతాము.

ఈ దశలో మనకు సాధారణంగా పనిచేసే వ్యవస్థ ఉంది. దీని కోడ్ కొంత సరళీకృత రూపంలో ఉంది:

public sealed class LagCompensationSystemGroup : ExecutableSystem
{
     //Машина времени
     private readonly ITimeMachine _timeMachine;

     //Набор систем лагкомпенсации
     private readonly LagCompensationSystem[] _systems;
     
     //Наша реализация кластеризатора
     private readonly TimeTravelMap _travelMap = new TimeTravelMap();

    public LagCompensationSystemGroup(ITimeMachine timeMachine, 
        LagCompensationSystem[] lagCompensationSystems)
     {
         _timeMachine = timeMachine;
         _systems = lagCompensationSystems;
     }

     public override void Execute(GameState gs)
     {
         //На вход кластеризатор принимает текущее игровое состояние,
         //а на выход выдает набор «корзин». В каждой корзине лежат энтити,
         //которым для лагкомпенсации нужно одно и то же время из истории.
         var buckets = _travelMap.RefillBuckets(gs);

         for (int bucketIndex = 0; bucketIndex < buckets.Count; bucketIndex++)
         {
             ProcessBucket(gs, buckets[bucketIndex]);
         }

         //В конце лагкомпенсации мы восстанавливаем физический мир 
         //в исходное состояние
         _timeMachine.TravelToTime(gs.Time);
     }

     private void ProcessBucket(GameState presentState, TimeTravelMap.Bucket bucket)
     {
         //Откатываем время один раз для каждой корзины
         var pastState = _timeMachine.TravelToTime(bucket.Time);

         foreach (var system in _systems)
         {
               system.PastState = pastState;
               system.PresentState = presentState;

               foreach (var entity in bucket)
               {
                   system.Execute(entity);
               }
          }
     }
}

వివరాలను కాన్ఫిగర్ చేయడం మాత్రమే మిగిలి ఉంది:

1. సమయం లో కదలిక యొక్క గరిష్ట దూరాన్ని ఎంత పరిమితం చేయాలో అర్థం చేసుకోండి.

పేలవమైన మొబైల్ నెట్‌వర్క్‌ల పరిస్థితుల్లో గేమ్‌ను వీలైనంతగా యాక్సెస్ చేయగలిగేలా చేయడం మాకు చాలా ముఖ్యం, కాబట్టి మేము కథనాన్ని 30 టిక్‌ల మార్జిన్‌తో (20 Hz టిక్ రేటుతో) పరిమితం చేసాము. ఇది చాలా ఎక్కువ పింగ్స్ వద్ద కూడా ప్రత్యర్థులను కొట్టడానికి ఆటగాళ్లను అనుమతిస్తుంది.

2. ఏ వస్తువులను సమయానికి తరలించవచ్చో మరియు ఏది చేయలేదో నిర్ణయించండి.

మేము, వాస్తవానికి, మా ప్రత్యర్థులను కదిలిస్తున్నాము. కానీ ఇన్స్టాల్ చేయగల శక్తి షీల్డ్స్, ఉదాహరణకు, కాదు. ఆన్‌లైన్ షూటర్‌లలో తరచుగా చేసే విధంగా డిఫెన్సివ్ ఎబిలిటీకి ప్రాధాన్యత ఇవ్వడం మంచిదని మేము నిర్ణయించుకున్నాము. ఆటగాడు ఇప్పటికే ప్రస్తుతం షీల్డ్‌ను ఉంచినట్లయితే, గతం నుండి లాగ్-పరిహారం పొందిన బుల్లెట్‌లు దాని గుండా ఎగరకూడదు.

3. డైనోసార్ల సామర్థ్యాలను భర్తీ చేయడం అవసరమా అని నిర్ణయించుకోండి: కాటు, తోక సమ్మె మొదలైనవి. మేము ఏది అవసరమో నిర్ణయించుకున్నాము మరియు వాటిని బుల్లెట్ల వలె అదే నియమాల ప్రకారం ప్రాసెస్ చేసాము.

4. లాగ్ పరిహారాన్ని ప్రదర్శించే ఆటగాడి కొలైడర్‌లతో ఏమి చేయాలో నిర్ణయించండి. మంచి మార్గంలో, వారి స్థానం గతంలోకి మారకూడదు: ఆటగాడు ఇప్పుడు సర్వర్‌లో ఉన్న సమయంలోనే తనను తాను చూసుకోవాలి. అయినప్పటికీ, మేము షూటింగ్ ప్లేయర్ యొక్క కొలైడర్‌లను కూడా వెనక్కి తీసుకుంటాము మరియు దీనికి అనేక కారణాలు ఉన్నాయి.

ముందుగా, ఇది క్లస్టరింగ్‌ను మెరుగుపరుస్తుంది: మేము క్లోజ్ పింగ్‌లతో ఉన్న ఆటగాళ్లందరికీ ఒకే భౌతిక స్థితిని ఉపయోగించవచ్చు.

రెండవది, అన్ని రేకాస్ట్‌లు మరియు అతివ్యాప్తిలలో సామర్థ్యాలు లేదా ప్రక్షేపకాలను కలిగి ఉన్న ప్లేయర్ యొక్క కొలైడర్‌లను మేము ఎల్లప్పుడూ మినహాయిస్తాము. డినో స్క్వాడ్‌లో, ఆటగాళ్ళు డైనోసార్‌లను నియంత్రిస్తారు, ఇవి షూటర్ ప్రమాణాల ప్రకారం ప్రామాణికం కాని జ్యామితిని కలిగి ఉంటాయి. ఆటగాడు అసాధారణ కోణంలో షూట్ చేసినా మరియు బుల్లెట్ యొక్క పథం ఆటగాడి డైనోసార్ కొలైడర్ గుండా వెళుతున్నప్పటికీ, బుల్లెట్ దానిని విస్మరిస్తుంది.

మూడవదిగా, మేము డైనోసార్ యొక్క ఆయుధం యొక్క స్థానాలను లేదా లాగ్ పరిహారం ప్రారంభానికి ముందే ECS నుండి డేటాను ఉపయోగించి సామర్థ్యాన్ని వర్తించే పాయింట్‌ను గణిస్తాము.

ఫలితంగా, లాగ్-కంపెన్సేటెడ్ ప్లేయర్ యొక్క కొలైడర్‌ల యొక్క నిజమైన స్థానం మాకు ముఖ్యం కాదు, కాబట్టి మేము మరింత ఉత్పాదక మరియు అదే సమయంలో సరళమైన మార్గాన్ని తీసుకున్నాము.

నెట్‌వర్క్ జాప్యాన్ని తీసివేయడం సాధ్యం కాదు, అది మాస్క్‌గా మాత్రమే ఉంటుంది. మారువేషంలో ఏ ఇతర పద్ధతి వలె, సర్వర్ లాగ్ పరిహారం దాని లావాదేవీలను కలిగి ఉంటుంది. ఇది షూట్ చేయబడిన ఆటగాడి ఖర్చుతో షూటింగ్ చేస్తున్న ప్లేయర్ యొక్క గేమింగ్ అనుభవాన్ని మెరుగుపరుస్తుంది. డినో స్క్వాడ్ కోసం, ఇక్కడ ఎంపిక స్పష్టంగా ఉంది.

వాస్తవానికి, సర్వర్ కోడ్ యొక్క పెరిగిన సంక్లిష్టత ద్వారా ఇవన్నీ కూడా చెల్లించవలసి ఉంటుంది - ప్రోగ్రామర్లు మరియు గేమ్ డిజైనర్లకు. అంతకుముందు అనుకరణ వ్యవస్థల యొక్క సాధారణ సీక్వెన్షియల్ కాల్ అయితే, లాగ్ పరిహారంతో, సమూహ లూప్‌లు మరియు శాఖలు అందులో కనిపించాయి. మేము పని చేయడానికి సౌకర్యంగా ఉండటానికి చాలా కృషి చేసాము.

2019 వెర్షన్‌లో (మరియు కొంచెం ముందుగా ఉండవచ్చు), యూనిటీ స్వతంత్ర భౌతిక దృశ్యాలకు పూర్తి మద్దతును జోడించింది. మేము వాటిని అప్‌డేట్ చేసిన వెంటనే సర్వర్‌లో అమలు చేసాము, ఎందుకంటే మేము అన్ని గదులకు సాధారణమైన భౌతిక ప్రపంచాన్ని త్వరగా వదిలించుకోవాలనుకుంటున్నాము.

మేము ప్రతి గేమ్ గదికి దాని స్వంత భౌతిక దృశ్యాన్ని అందించాము మరియు అనుకరణను లెక్కించే ముందు పొరుగు గది డేటా నుండి దృశ్యాన్ని "క్లియర్" చేయవలసిన అవసరాన్ని తొలగించాము. మొదట, ఇది ఉత్పాదకతలో గణనీయమైన పెరుగుదలను ఇచ్చింది. రెండవది, కొత్త గేమ్ ఎలిమెంట్‌లను జోడించేటప్పుడు ప్రోగ్రామర్ సీన్ క్లీనప్ కోడ్‌లో పొరపాటు చేస్తే ఉత్పన్నమయ్యే మొత్తం తరగతి బగ్‌లను వదిలించుకోవడాన్ని ఇది సాధ్యం చేసింది. ఇటువంటి లోపాలు డీబగ్ చేయడం కష్టం, మరియు అవి తరచుగా ఒక గది దృశ్యంలో భౌతిక వస్తువులు మరొక గదిలోకి "ప్రవహించే" స్థితికి దారితీస్తాయి.

అదనంగా, భౌతిక ప్రపంచ చరిత్రను నిల్వ చేయడానికి భౌతిక దృశ్యాలను ఉపయోగించవచ్చా అనే దానిపై మేము కొంత పరిశోధన చేసాము. అంటే, షరతులతో, ప్రతి గదికి ఒక సన్నివేశాన్ని కాకుండా, 30 సన్నివేశాలను కేటాయించండి మరియు వాటిలో కథను నిల్వ చేయడానికి ఒక సైక్లిక్ బఫర్‌ను తయారు చేయండి. సాధారణంగా, ఎంపిక పని చేస్తుందని తేలింది, కానీ మేము దానిని అమలు చేయలేదు: ఇది ఉత్పాదకతలో ఎటువంటి వెర్రి పెరుగుదలను చూపించలేదు, కానీ ప్రమాదకర మార్పులు అవసరం. ఇన్ని సన్నివేశాలతో ఎక్కువసేపు పనిచేసినప్పుడు సర్వర్ ఎలా ప్రవర్తిస్తుందో ఊహించడం కష్టమైంది. కాబట్టి, మేము నియమాన్ని అనుసరించాము: "అది విరిగిపోకపోతే, దాన్ని పరిష్కరించవద్దు".

మూలం: www.habr.com

ఒక వ్యాఖ్యను జోడించండి