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.