Používáme 39dll

Pong pro dva hráče pomocí UDP a TCP

 

V tomto tutoriálu Vás naučím jak používat winsock dll pro vytvoření online hry použitím UDP i TCP.

 

Výhody UDP

Velká výhoda UDP je rychlost a malá náročnost na připojení. UPD je o hodně rychlejší než TCP a může být využito ve hře, kde je hlavní packet posílán nepřetržitě. UDP je také nevyžaduje propojení (je connectionless - pzn. překl.) mezi počítači. Prostě pošleš zprávu na správnou IP a port a je hotovo.

 

Nevýhody UDP

Jedna nevýhoda používání UDP je nespolehlivost. Neexistuje žádná záruka, že dorazí všechny packety. Proto pokaždé otevíráme i TCP připojení abychom mohli důležité packety odesílat pomocí TCP. Packety jako hlavní packet by měli být poslány pomocí UDP, protože i když nedorazí, další přichází hned za ním.

 

Další velká nevýhoda jsou problémy s firewallem. UDP vyžaduje nastavený firewall stejně jako hostující hra (server hry - pozn. přek.) s TCP. Takže s UDP musejí mít i klienti nastavený firewall.

 

Inicializace dll.

Aby dll fungovala, musíte vložit jednoduchý script co Creation Code v první místnosti hry:

dllinit(0, true, false);

 

Když je první argument číslo tak dll načte soubor "39dll.dll". Pokud je string (=řetězec pozn. překl.), bude string cesta k souboru s dll. (např. pokud bude "sock.dll", načte se dll ze souboru "sock.dll", ne z "39dll.dll").

Druhý argument určuje, zda chcete používat winsock (true=ano, false=ne). V tomto případě ho používat budeme.

Třetí argument by měl být true pouze pokud chcete používat souborové funkce. V tomto příkladu to není potřeba, takže nechceme načítat tyto funkce.

 

Nastavení serveru.

Pro vytvoření multiplayerové hry musí jeden člověk hostovat server a ostatní se k němu musí připojit.

Pro nastavení serveru použitím dll musíte nejdřív vytvořit objekt, který bude kontrolovat nastavování serveru a přijímaní nových připojení. Budete si muset vytvořit menu se 2 tlačítky. První bude mít text "Host" a druhý by měl mít text "Connect". Když uživatel vybere host, vykoná se kód "global.master=true;" a změní místnost na místnost "rmWaiting". Vytvořte nový objekt a pojmenujte ho "objWait". Do eventu Create vložte tento kód:

 

listen = tcplisten(14804, 2, true);

if(listen <= 0)

{

  show_message("Failed to listen on port 14804");

  game_end();

}

Tento kus kódu vytvoří naslouchací (listening) socket, který bude čekat na jakékoli připojení na port 14804. Port může být jakékoli číslo, ale já jsem použil 14804.

Druhý argument je maximální povolený počet připojení ve vyčkávacím seznamu. Ale není to maximální počet lidí ve hře. Když se někdo pokusí, připojit vloží ho to na seznam čekajících, dokud ho server nepřijme.

Poslední argument je nastavený na true, protože nechceme, aby hra zamrzla, když používáme tcpaccept() a nikdo se nepokouší připojit. Script bude vracet číslo id větší jak 0 pokud se někdo připojí. Číslo menší jak 0 vrací v případě chyby.

Další řádek kontroluje, zda úspěšně posloucháme na portu 14804.

 

Přijímání nových připojení

Pro přijetí nových připojení musíte vytvořit step event pro objekt "objWait" (který jsme si už vytvořili). Vložte do něj kód:

 

client = tcpaccept(listen, true);

if(client <= 0) exit;

global.udpsock = udpconnect(14805, true);

global.otherplayer = client;

global.otherip = lastip();

global.otherudpport = 14803;

room_goto(rmGame);

 

První řádek kontroluje seznam čekajících, aby zjistil, jestli se někdo nepokus připojit k naslouchacímu socketu. Když se nikdo nepřipojil, vrátí číslo menší než 1. Když se někdo připojil tak vytvoří nový socket a vrátí jeho id. Tento socket bude použit pro odesílání a přijímání dat od člověka, který se právě připojil. Druhý argument v tcpaccept znamená, že socket bude non-blocking. V tom případě hra nezamrzne, pokud nebude žádná zpráva k přijetí.

Druhý řádek kontroluje zda tcpaccept() vrátil chybu. Pokud vrátil tak se ukončí script.

 

Každý další řádek kódu po druhém řádku bude proveden, pouze pokud nevrátí žádnou chybu. Třetí řádek vytváří udp socket, který bude použit pro odesílání zpráv pomocí UDP protokolu. Další řádek nastavuje proměnou "global.otherplayer" na id socketu který tcpaccept() vrátil. Další řádek zjistí IP adresu naposledy přijatého hráče a vloží jí do proměnné "global.otherip". Tohle použijeme při odesílání UPD zprávy druhému hráči. Další řádek ukládá port, který používá UDP socket na vzdáleném počítači do proměnné „global.otherudpport“. Využijeme to při posílání UDP zprávy druhému hráči. Poslední řádek přesouvá do herní room. (Musíte si jí vytvořit).

 

Připojení k serveru.

Pro připojení ke hře se musíte připojit k serveru. V místnosti, kde mate tlačítka “Host” a “Connect”, udělejte, že když člověk klikne na “Connect” provede následující kód:

 

global.master = false;

server = tcpConnect("127.0.0.1", 14804, true);

if(server <= 0)

{

  show_message("Unable to connect to server");

  game_end();

  exit;

}

global.otherplayer = server;

global.udp = udpconnect(14803, true);

global.otherip = tcpip(server);

global.otherudpport = 14805;

room_goto(rmGame);

 

První řádek nastaví global.master na false, protože nejsme server. Jsme klient, který se připojuje k serveru. Druhý řádek vytváří aktuální připojení k serveru.

První argument v tcpConnect je ip adresa ke které se chcete připojit. Když právě testujete na svém pc, použijte adresu „127.0.0.1“.

Druhý argument je číslo portu, ke kterému se chceme připojit. Třetí argument určuj, zda použít blocking, nebo non blocking mód. Nastavíme ho na true, což znamená non blocking. To zajistí, že když se pokusíme odeslat, nebo přijmout zprávu, hra nezamrzne, dokud se operace nedokončí. Pokud tcpConnect úspěšně připojilo a bylo přijato serverem, proměnná „server“ by nyní měla obsahovat socket id. Když se vyskytne chyba, vrátí funkce číslo menší než 1.

 

Další řádek kontroluje, jestli se nevyskytla chyba. Chyba může být, když server neexistuje, nebo nás nepřijme. Když dojde k chybě, hra se ukončí.

 

Když nenastane žádná chyba, do proměnné “otherplayer” se vloží socket id, které je v proměnné “server”. Pak se otevře UDP socket na portu “14803” a nastaví se na non-blocking mod. Další řádek dostává IP adresu serveru a ukládá jí do proměnné “global.otherip”, která bude použita pro posílání UDP zpráv. Další řádek zjišťuje číslo portu, které používá host pro jeho UDP socket a vkládá jej do proměnné “global.otherudpport”.

Poté se přesuneme do herní místnosti pomocí room_goto().

 

Odesílání a přijímání zpráv

Aby naše hra fungovala, potřebujeme znát y pozici pálky, kterou ovládá druhý hráč a klient musí vědět x, y pozici míčku, který bude ovládán serverem.

 

 

Odesílání

Pálka, kterou kontrolujete Vy, musí odesílat Y pozici druhému hráči, aby mohl vykreslovat pálku na správné pozici.

Abychom toho dosáhli, vložte následující kód do keyboard UP a keyboard DOWN eventu:

 

clearbuffer();

writebyte(0);

writeshort(y);

sendmessage(global.udpsock, global.otherip, global.otherudpport);

 

První řádek vyčistí interní buffer od všech dat. Používáme to v případě, že by v bufferu již nějaká data byla. Druhý řádek zapíše byt, který představuje Message ID. Ve hře bude message id 0 představovat zprávu obsahující y pozici.

Další řádek zapíše aktuální Y pozici do bufferu. Vybrali jsme datový typ „short“ protože short může být jakékoli číslo mezi -32000 a 32000. Short využívá 2 byty. Kdybychom použili jeden byt reprezentující Y pozici a Y pozice by byla větší jak 255 tak to co dostanete, nebude číslo, které jste chtěli. Poslední řádek odešle všechna data v interním bufferu druhému hráči skrz UDP socket. Druhý argument v sendmessage je IP počítače kterému posíláte zprávu. Třetí argument je vzdálený port přijímajícího. V tomto případě jsou data Message Id a 2 byty použité pro Y pozici.

 

Nyní, pokud jsme server, potřebujeme poslat x, y pozici míčku druhému hráči.

Aby se tak stalo, vložte následující kód do Step eventu.

 

if(!global.master)exit;

clearbuffer();

writebyte(1);

writeshort(x);

writeshort(y);

sendmessage(global.udpsock, global.otherip, global.otherudpport);

 

První řádek kontroluje, jestli jsme server. Když nejsem server tak ukončí script a nespustí kód za ním.

Nicméně když jsem server, tak první vyčistíme interní buffer. Nyní zapíšeme Message Id “1”, které bude označovat zprávu jako pozici míčku. Nyní zapíšeme short, který bude představovat x pozici a další short, který bude představovat y pozici.

Teď prostě pošleme zprávu druhému hráči pomocí UDP socket použitím jeho IP a vzdáleného portu.

 

Posílání zpráv do chatu

Níže je kód, který použijeme pro posílání řetězců (strings):

 

clearbuffer();

writebyte(2);         //chat message id

writestring(“Hello other player”);

sendmessage(global.otherplayer);

 

Druhý řádek je ID, které budeme používat pro chat zprávy. Třetí řádek je použitý pro zapsání chat zprávy jako řetězce do buffer. Poslední řádek odešle zprávu pomocí TCP portu protože chceme aby zpráva určitě dorazila. (viz. Nevýhody UDP)

 

Přijímaní zprávy

 

U pálky, kterou neovládáte…ta která je ovládána druhým hráčem, vložte do Step eventu tento kód:

 

var size;

while(true)

{

  size = receivemessage(global.udpsock);   //try receive a message on the udp socket

  if(size <= 0) size = receivemessage(global.otherplayer); //if no udp message try receive a tcp message

  if(size < 0) break;

  if(size == 0)

  {

    show_message("The other player left the game");

    game_end();

  }

  messageid = readbyte();

  switch(messageid)

  {

    case 0:

      y = readshort();

    break;

   

    case 1:

      objBall.x = readshort();

      objBall.y = readshort();:

    break;

 

  case 2:

    chatmessage = readstring();

    show_message(chatmessage);

  break;

  }

}

 

Jako první vytvoří, použitím while(true), nekonečnou smyčku. První řádek smyčky přijme kterékoli zprávy od druhého hráče, které byly odeslány do UDP socketů a nastaví proměnnou “size” na velikost přijatých dat v bytech.

Druhý řádek kontroluje, jestli nebyla přijata žádná zpráva. Když nebyla přijata žádná zpráva tak zkusí přijmou TCP zprávu. Když nebyla přijata žádná TCP zpráva, tak ukončí smyčku.

Další řádek kontroluje, jestli se druhý hráč odpojil ze hry. Když je velikost zprávy 0, znamená to, že hráč opustil hru.

Když hráč hru opustil, hra se ukončí. Když přijmeme zprávu, tak se všechna data ze zprávy zapíší do interního bufferu. Nyní můžeme používat buffer příkazy pro přečtení dat ze zprávy. První část vrací Message ID (“messageid = readbyte()”.)

Poté použije switch pro kontrolu pro přiřazení ID k akci. Když je ID 0, znamená to, že zpráva je pro ostatní hráče y pozice. Jednoduše použijeme readshort() pro zjištění y pozice. Zapamatujete si, že protože jsme vepsali y pozici jako short, musíme číst y pozici použitím short.

 

Když je Message ID rovno 1, což představuje x, y pozici míčku, tak použijme readshort() pro nastavení x míčku na správné místo a znovu readshort() pro nastavení y pozice míčku na správné místo.

 

Když je Message ID rovno 2, což představuje chat zprávu, tak zkopírujeme řetězec do proměnné “chatmessage” použitím “readstring()” a zobrazíme chat zprávu.

 

 

Uvolnění dll.

Když nechcete žádné ošklivé errory když ukončíte hru, tak musíte uvolnit dll z paměti. Abyste tak udělali, vytvořte objekt, který bude v každé místnosti. Nyní vložte do eventu Game End kód “dllfree()”, což uvolní winsock a uvolní všechnu paměť použitou interním bufferem.

 

Zavírání socketů.

Když skončíme s dll, měli bychom zavřít všechny sockety otevřené během hry. Což jednoduše uděláme použitím “closesocket(socketid);” na všechny sockety které jsme použili. Nezapomeňte zavřít také naslouchací socket.