Ajattelin jatkossa kysellä teiltä kokeneemmilta verkkopeliin liittyviä kysymyksiä. Tarkoituksena on tehdä 3D pong (Flash peli Curveball tyyppinen) kaksinpelimahdollisuudella verkon yli. SDL hoitaa inputtia ja ääniä, OpenGL piirtää grafiikan. Perus idea on aika hyvin selvillä, mutta ajttelin kysyä mielipidettänne muutamasta käytännön toteutuksesta.
Pelin aloittamisen voisi tehdä siten, että pitää antaa toisen pelaajan ip osoite. Tämä ei kuitenkaan ole kovin kätevää, niin ajattelin tehdä pöytäkoneestani keskuspalvelimen mikä pitää kirjaa niistä jotka odottelevat peliseuraa. Php on aika hyvin hallussa, niin olisiko tyhmä idea toteuttaa tuo systeemi php:llä? Ohjelma läheittäisi http kutsuja php skriptiin ja php pitäisi kirjaa avoimista peleistä jne. Toinen vaihtoehto olisi tehdä C:llä serveri ohjelma pyörimään johonkin porttiin.. Mielummin tekisin kuitenkin phpllä, mutta onko se ihan hölmö idea?
Kun peli on saatu aloitettua ja molemmat pelaajat ovat löytäneet toisensa, reaaliaikainen viestintä kannattaa ilmeisesti toteuttaa keyvellä UDP protokollalla. Koska pallon kimpoamissuunta riippuu siitä mihin kohtaan mailaa se osuu, pitää miettiä miten molemmat pelaajat saavat oikean tiedon pallon suunnasta. Verkossa on kuitenkin viivettä mahdollisesti jopa 50 ms, niin toinen pelaaja saattaisi (ainakin muutaman pompun jälkeen) nähdä pallon eri kohdassa kuin toinen. Ajattelin, että molemmat lähettävät noin 10 - 20 ms väelin paketteja toisilleen, joissa on seuraavat datat:
- Ohjelman SDL_GetTicks(), niin tiedetään mikä paketti on uusin
- Mailan x ja y (ikkunan koko voisi olla kiinteä 700 x 800 pikseliä molemmilla). Toisaalta OpenGL käyttää mailan paikasta liukulukua välillä -1.0 - 1.0, liukuluku olisi muistivaatimukseltaan yhtä iso kuin int.
- Pallon x, y, z, vx, vy, vz, rotx, roty, rotz, rotxv, rotyv, rotzv
Eli paikka, nopeus, kiertokulma (saattaa tulla tekstuuri), kulmanopeudet
- Montako kertaa pallo on kimmonnut mailasta
Ajattelin, että pallon "todellinen" nopeus lasketaan aina sen pelaajan näkökulmasta, jonka vuoro on lyödä palloa. Sillä aikaa kun pallo lentää kohti kentän toistapäätä, on noin sekuntti aikaa saada molemmat pelaajat yheisymmärrykseen pallon parametreistä.
Kokoa tulisi noin 4 + 2*4 + 12*4 + 4 = 64 tavua (4 tavua / int tai float). UDP paketti sisältää ilmeisesti vähän dataa ipistä ja jonkun checksummin. Paketti olisi siis max 128 tavua. Jos näitä lähetetään 100 kertaa sekunnissa, liikennettä tulee noin 12 kt/s. Kuulostaa periaatteessa aika paljolta.. Ehkä pitäisi pyrkiä lähettämään 50 pakettia sekunnissa? Vai vielä harvemmin ja ohjelma yrittäisi laskea seuraavan paikan mailan edellisistä tiedoista arvaamalla?
En ole aiemmin tällaista tehnyt niin nämä käytännön asiat hieman mietityttävät.
Taikka sitten yksikertaisesti lähetät vain pelaajille toistensa ohjaussingaalit. Esim. pallon ja mailojen sijaintia ei tarvitse lähettää, sillä käyttäjän suunnalla ne laskettaisiin tulleiden ohjaussingaalien mukaan. Tämä tietenkin vaatii sen tarkistamisen että kummankin koneet ehtivät hoitaa hommansa, mutta datamäärä pysyy pienenä. Kutakuinkin tälläinen toteutus on ainakin Doomin kanssa verkossa. Taitaa myös UDP olla silläkin käytössä jos en väärin muista..
-Grey-
Jos pallo tulee kohti pelaajaa A, ja A lyö pallon kohti B:tä, niin B näkee A:n mailan sijainnin hieman eri kohdassa kuin A, johtuen pienestä viiveestä. Muutaman lyönnin jälkeen pelaajat saattavat nähdä pallon ihan eri paikassa..
*Huoh* Sen viiveen vuoksi katsotaan että kumpikin ovat valmiita. Katsotaan mitä saan rautatangosta väännettyä:
-Pelin aloitus. Mailat ja pallo ovat kummallakin puolella täsmälleen samassa pisteessä.
-Pelaaja A painaa näppäintä. Tieto menee pelaajalle B että pelaaja A painoi näppäintä.
-Nytten kumpikin laskee että osuttiin palloon ja kumpikin laskee sille saman suunnan.
-Nyt katsotaan että kumpikin on valmis. Jos pelaaja A esim. on ehtinyt laskea sijainnin, mutta pelaaja B ei, A odottaa kunnes pelaaja B:n suunnalla ollaan valmiita.
Mitä tulee viiveeseen, niin pakettihäviöt aiheuttavat sitä varmana pahemmin ja syntyy lagia. Siksi paketti kannattaa pitää niin pienenä kuin mahdollista ja lähettää vain tärkein. Koska pelisi ei ilmeisemmin sisällä kovinkaan monimutkaisia toimintoja, kaikkea tietoa ei kannata lähettää. Jos systeemi toteutetaan oikein, riittää tosiaan:
a)Näppäinpainallusten lähetys.
b)Singaalin "Olen valmis, dorka!" lähetys.
Eli näin pääset myös todella pieniin tavumääriinkin. Riippuen toteutuksesta, ehkä neljään tavuun. 4x100=400 tavua, tai tavun pienempi tahi suurempi. Ja tosiaan, laskenta voidaan suorittaa pelaajien päässä, eikä siinä ole muita esteitä kuin se että prosessori laskee väärin, mutta jos se huolettaa, muodosta tarkistussumma, joka myös lähetetään. Eli jos pelaaja B laskee väärin, niin hylätään pelaajalla A ja otetaan uusiksi.
Jos ei vieläkään miellytä, niin ole hyvä ja tutustu mainitsemani Doomin (siis, Doom I&II) systeemiin. Siinä on juuri näppäinpainallusten mukaan menevä. Pelikin on paljon monimutkaisempi, eli toisin sanoen, oikein tehtynä homma toimii..
-Grey-
Yksi mahdollisuus olisi tällainen: Kaikki lähettävät näppäintiedot palvelimelle. Palvelin pitää näistä kaikista kirjaa ja tiedottaa muille aina jokaisen framensa jälkeen, mitä on pohjassa ajanhetkellä t. Kukin pelaaja taas saa suorittaa tuon kyseisen framen vasta, kun sitä vastaava paketti on tullut perille.
Jos haluaa hoitaa homman niin, että peli ei nytkähtele nettiyhteyden nopeuden mukaan, voi tietysti vielä määrätä pelaajille tietyn vakioviipeen, jolloin kaikki sitä pienempi myöhästely jää sinänsä huomaamatta ja reaktioaika on vakio. Näin on havaintojeni mukaan toteutettu esimerkiksi StarCraft. Strategiapeleissä toki pienen lagin vaikutus on lopulta pienempi kuin vaikkapa räiskintäpeleissä, joten toteutuskin lienee erilainen.
Grey kirjoitti:
Mitä tulee viiveeseen, niin pakettihäviöt aiheuttavat sitä varmana pahemmin ja syntyy lagia. Siksi paketti kannattaa pitää niin pienenä kuin mahdollista ja lähettää vain tärkein. Koska pelisi ei ilmeisemmin sisällä kovinkaan monimutkaisia toimintoja, kaikkea tietoa ei kannata lähettää.
Jos lagia ei olisi, tarvittaisiin toiselta ainoastaan mailan sijainti. Mailan liikuttelu tapahtuu hiirellä. Pakettihäviöitä voi kompensoida siten, että mailan paikka pyritään ennustamaan edellisten paikkojen perusteella, mikäli pakettia ei tule perille.
lainaus:
Sen viiveen vuoksi katsotaan että kumpikin ovat valmiita.
Vaikka molemmat ovat valmiita ottamaan/lähettämään paketin, se ei kuitenkaan poista viivettä. Oletetaan että alussa molemmat ovat samassa tilanteessa: Maila A on pisteessä (0, 0, -1), maila B (0, 0, 1). Pallo liikkuu z-akselin suunnassa ja on aluksi origossa. Pallon nopeus on (0, 0, 0.5) (yksikköä / sekunti). Kahden sekunnin kuluttua pallo siis osuu pelaajan B mailaan. Jos viive on 50 ms ja pelaaja siirtää mailaansa 1975 ms jälkeen, pelaaja A luulee että pallo osui keskelle mailaa. Pelaajan B mielestä maila kuitenkin ehti siirtyä ennen kuin pallo osui siihen. Pallon kimpoamissuunta riippuu siitä mihin kohtaa mailaa se osuu, joten A ja B ovat eri mieltä pallon nopeudesta ja paikasta. Luullakseni jo 10 lyönnin jälkeen pallon paikassa on huomattava ero pelaajien A ja B välillä. Tätä eroa ei tietenkään saisi syntyä, jotta pelattavuus säilyisi.
Yksi vaihtoehto olisi, että molemmat pelaajat laskevat pallon parametrejä oman ja toisen pelaajan arvojen (painotettuna?)keskiarvona. Näin arvot lähestyisivät toisiaan, ja noin 20 paketin jälkeen olisivat hyvin lähellä toisiaan. Näin myös suuret erot tasoittuisivat nopeammin kuin pienet.
Koska tämä on verkkopeleissä ihan perustavaa laatua oleva ongelma, tähän on varmasti keksitty jotain fiksuja ratkaisuja. Täytynee etsiä enempi. Toivoisin kuitenkin saavani myös tähän threadiin vastauksia, jos vain mahdollista.
Se on huolistani pienin, että prosessori laskisi väärin :D
Vielä lisäisin tuosta paketin optimoinnista, että ensinnäkin pallon asentoa on turha lähettää, jos se ei vaikuta kuin ulkonäköön. Jos mailan sijainti säilytetään kokonaislukuformaatissa, niin siirtäminenkin kannattaa hoitaa kokonaislukuna. Tuossa tapauksessa ilmeisesti short riittäisi aivan hyvin, siitäkin lähti siis heti kaksi tavua.
Greyn selostuksessa on kyllä aivan selvästi nähtävissä tuo puute, että milläpä ne pelaajat osaavat samaan aikaan peliä jatkaa, kun B:n vastauksellakin kestää kuitenkin aikansa. Vai olettiko Grey kenties, että framet ovat vakiomittaisia? Se toki helpottaa asioita huomattavasti, mutta tällöin B:n vastaus on minun nähdäkseni turha ja vain lisää lagia.
Keskustelusta käy ilmi, että huijaamista ei tarvitse ottaa huomioon, joka tekee ongelmasta hyvin yksinkertaisen.
Kun pelaajan maila osuu palloon, hän laskee uuden suunnan sekä lähettää vastapuolelle tiedon pallon sijainnista, liike suunnasta ja nopeudesta(+pallon pyörimiset ja muut ylimääräset kilkkeet). Suosittelen TCP:n käyttöä, jotta viestit menevät varmasti perille.
Mailojen liikkeet voi hoitaa lähettelemällä sopivin välein niiden sijainnin ja laskea vastaanottajan päässä "smoothit" liikeradat. Tähän voi harkita käyttää UDP:tä.
Jos viive on iso, saattaa näyttää hassulta, kun pallo menee vastustajan mailan sisää(tai läpi) ja sitten se taas on yhtäkkiä pelikentällä...
Edit: pientä tarkennusta tekstiin.
Kannattaa toisaankin lähettää vastustajalle vain pallon osumakohta ja osumisaika aina kun itse osuu palloon. (tai "hävisin" viesti, jos ei osu) Näistä tiedoista saa laskettua pallon paikan seuraavaan osumaan asti. Myös mailan paikkatietoja voi hiljakseltaan (esim 15 kertaa sekunnissa) lähetellä vastustajalle, jolloin peli pyörii mukavasti ja vielä ilman minkäänlaista viivettä kunhan pallon lentoaika puolelta toiselle on pidempi kuin verkon lagi.
Pallon paikan voi siis yrittää ennustaa siihen asti, kunnes vastustajan "lyöntiviesti" saapuu ja sen jälkeen korjata pallon tilan oikeaksi. Eli lasketaan pallon ja vastustajan mailan paikkaa normaalisti: x += v*dt
, kunnes viesti saapuu, jolloin korjataan tilanne: v = v1; x = x1 + v*(t - t1);
missä x1
on paikka, jossa pallo (tai maila) oli vastustajan mielestä (oikeasti) hetkellä t1
ja t
on nykyinen aika. v1
on pallon uusi nopeus.
Clienttien kellot täytyy siis jotenkin saada synkronoitua keskenään (mikä on vaikeaa), jotta pelin saa pyörimään mukavasti myös viiveen kanssa. Voi kyllä olla jotain kikkoja, jolla tämän ongelman voi kiertää.
Jos peliä pyörittää tuolla muun muassa Metabolixin mainitsemalla tyylillä (samat komennot -> sama suoritus), mikä siis varsinkin raskaammissa peleissä toimii todella hyvin, niin kannattaa muistaa, että tällöin ohjelma ei kestä minkäänlaisia poikkeavuuksia laskennassa eri clienttien välillä. Ohjelma hajoaa välittömästi käsiin pienimmästäkin virheestä (Pong on tässä mielessä sen verran hidastempoinen peli, että hajoamiseen voi mennä muutamia lyöntejä)
Kannattaa käyttää vekkoliikenteeseen jotakin valmista systeemiä, kuten ENettiä, joka toimii UDP:n päällä, mutta huolehtii siitä, että kaikki paketit tulevat perille oikeassa järjestyksessä (nopeammin kuin TCP). Elämästä tulee äkkiä hyvin hankalaa, kun joutuu huolehtimaan siitä, että paketteja putoaa matkalla. Myös TCP saattaa tässä tapauksessa toimia ihan riittävän nopeasti ja on kaikin puolin paljon helpompi kuin UDP. Ei kuitenkaan kannata käyttää useampaa systeemiä yhtä aikaa, jos ei ole ihan pakko.
Ja ihan tiedoksi, että sekä UDP:llä että TCP:llä verkon vasteaika kasvaa radikaalisti liikennemäärän noustessa, vaikka kaistaa löytyisi kuinka. Esimerkiksi jos yrität 100M-nettiyhteydellä lähettää 12kt/s TCP:llä, niin ping nousee hyvin äkkiä tähtitieteellisiin lukemiin. Kannattaa siis pitää pakettikoko mahdollisimman pienenä. Ja ottaen huomioon, että silmä erottaa korkeintaan 25 kuvaa sekunnissa, niin yli 30fps ei varmaan kannata lähettää yhtään mitään.
Counterissa (ainakin 1.6) "pro" pelaajat pitävät ehdottoman tärkeänä, että FPS olisi 100. Elokuvien 25 fps lienee lomitettu, eli oikeastaan 50 fps olisi tavoiteltava ja 60 fps sopiva maksimi.
Siksi UDP sopisi tähän hyvin, että jos paketti ei mene perille niin sitä on enää turha lähettää uusiksi, koska tieto on jo vanhentunutta. Siksi pakettiin voisi pistää SDL_GetTicks() 4 tavua, niin toinen tietää mikä niistä paketeista on uusin. Kiitoksia vastauksista :) Pitää kokeilla noita ehdotuksia käytännössä.
Entä kuulostaako hölmöltä toteuttaa tuota pelien aloitusta phpllä? Ohjelma voisi lähettää http pyyntöjä ja pieni määrä dataa välittyisi helposti $_GET kentässä. Mielestäni ratkaisu ei olisi yhtään hullumpi.
msdos464 kirjoitti:
Counterissa (ainakin 1.6) "pro" pelaajat pitävät ehdottoman tärkeänä, että FPS olisi 100. Elokuvien 25 fps lienee lomitettu, eli oikeastaan 50 fps olisi tavoiteltava ja 60 fps sopiva maksimi.
Ei kai ihmissilmä ees erota 100 FPS:ia?
Newb kirjoitti:
msdos464 kirjoitti:
Counterissa (ainakin 1.6) "pro" pelaajat pitävät ehdottoman tärkeänä, että FPS olisi 100. Elokuvien 25 fps lienee lomitettu, eli oikeastaan 50 fps olisi tavoiteltava ja 60 fps sopiva maksimi.
Ei kai ihmissilmä ees erota 100 FPS:ia?
Näin minäkin uskon.. Ajattelin kuitenkin pyrkiä 60 fps:ään, koska peli on kuitenkin niin kevyt ja yksinkertainen (pyörii helposti yli 200 fps vuoden vanhalla läppärillä).
msdos464 kirjoitti:
Counterissa (ainakin 1.6) "pro" pelaajat pitävät ehdottoman tärkeänä, että FPS olisi 100. Elokuvien 25 fps lienee lomitettu, eli oikeastaan 50 fps olisi tavoiteltava ja 60 fps sopiva maksimi..
Loimitusta käytetään televisiossa ja CRT-näytöllä (automaattisesti) vähentämään kuvan välkkymistä. Elokuvat on perinteisesti kuvattu filmille 24fps. Nopeissa liikkeissä pelissä saattaa (ainakin Wikipedian mukaan) erottaa isomman frameraten, koska siirtymiä framejen välillä (toisin, kuin kuvatussa materiaalissa) ei ole sumennettu siten, kuten ihmissilmä normaalisti ne näkisi.
msdos464 kirjoitti:
Siksi UDP sopisi tähän hyvin, että jos paketti ei mene perille niin sitä on enää turha lähettää uusiksi, koska tieto on jo vanhentunutta. Siksi pakettiin voisi pistää SDL_GetTicks() 4 tavua, niin toinen tietää mikä niistä paketeista on uusin.
Tuo aikaleima kannattaa laittaa senkin takia, että voit silloin pyörittää peliä eri framerateilla eri clienteillä. Pongissa varmaan toimii ihan hyvin se, että lähettää usein noita tilapäivityksiä pallon ja mailojen asennosta. Vaikka siten, että pallon paikan määrää se pelaaja, joka seuraavaksi on osumassa palloon. Ota kuitenkin huomioon, että jos väärä paketti hukkuu (esim. pelinloppumisviesti), tai pallo ylittää kentän verkon lagia nopeammin, niin olet ongelmissa.
os kirjoitti:
Tuo aikaleima kannattaa laittaa senkin takia, että voit silloin pyörittää peliä eri framerateilla eri clienteillä. Pongissa varmaan toimii ihan hyvin se, että lähettää usein noita tilapäivityksiä pallon ja mailojen asennosta. Vaikka siten, että pallon paikan määrää se pelaaja, joka seuraavaksi on osumassa palloon. Ota kuitenkin huomioon, että jos väärä paketti hukkuu (esim. pelinloppumisviesti), tai pallo ylittää kentän verkon lagia nopeammin, niin olet ongelmissa.
Ajattelin ihan samaa, eli mennään sen pelaajan "säännöillä" joka on lyöntivuorossa.
Vaikka viive olisi 100 ms, niin pallo saisi viilettää kentän läpi vielä ainakin 5 kertaa sekunnissa :D Aika kova tahti jos siihen vielä osuu.. mutta tietysti pitää miettiä näitä asioita jo etukäteen eikä vasta sitten kun ohjelma kaatuilee.
msdos464 kirjoitti:
Vaikka viive olisi 100 ms, niin pallo saisi viilettää kentän läpi vielä ainakin 5 kertaa sekunnissa :D Aika kova tahti jos siihen vielä osuu..
Viivehän ei pysy samana vaan vaihtelee monesta tekijästä riippuen. Hetkittäinen verkkoyhteyden huonontuminen (ihan mistä syystä tahansa) voi nostaa lagin parin framen ajaksi vaikka 400ms:n, jolloin käy huonosti, jos peli luottaa siihen, että viesti tulee aina ajoissa perille.
Pongin tapauksessa pelaajien välinen kommunikaatio on sen verran simppeliä, että epätavanomaisetkin verkkoliikenneratkaisut voivat toimia todella hyvin. Verkkopeliohjelmoinnista löytyy netistä todella huonosti resursseja. Kannattaa kokeilla vähän erilaisia vaihtoehtoja ja katsoa, mikä toimii parhaiten.
Aihe on jo aika vanha, joten et voi enää vastata siihen.