De 39dll.dll tutorial!
Door Rob Quist

Deze volledige tutorial en al zijn benodigdheden zijn hier te downloaden:
Download voleddige ZIP


Inhoud:
1 - Het Begin
    a. Inleiding
   b. Benodigdheden
   c. Basisuitleg
   d. Uitleg over tekens en overige in de text.
2 - Lets Start!
   a. Goed voorbereiden
    b. De Server opzetten
   c. De Client opzetten



Hoofdstuk 1
Het Begin

a. Inleiding

Zo. Dus je wilt een online game maken? Das eigenlijk heel makkelijk als je de basis hebt.
We gaan met deze tutorial een klein chat + loop exampletje maken met 1 grote server.
Oftuwel, we hebben 1 server, die 24 / 7 draait, waar de clients op kunnen inloggen en op kunnen spelen.

b. Benodigdheden

de 39dll.ddl (het bestand zlf)
gamemaker 6.0, 6.1 of 7.0 geregistreerd.
(In deze tutorial werk ik met Gm6.1)
Een goede kennis van GameMaker
Een goede kennis van GML
Een goede kennis van Engels
Een redelijke kennis van internet / ipadressen / pakketjes / poorten / sockets.

c. Basisuitleg

In 39dll.dll werk je met clients en servers.
Die communiceren via pakketjes die ze naar elkaar verzenden.
Deze pakketjes gaan via een protocool (tegenwoordig, vanaf win98 is het TCP/IP, en vroeger was dat UDP, en nog vroeger, in de middeleeuwen heette het POSTDUIF.)
een bepaalde poort uit (bij de server is dat voor TPC 12564, en voor UPD 12565. (maar UDP gaan we toch niet gebruiken))
en dan bij de client een bepaalde poort weer in (standaard is dat voor TPC 12564, en voor UPD 12565).

Byte    - Kan van 0 tot 255
String  - Is gewoon een "string". Een reeks letters en of cijfers dus.
Short   - kan inhoud bevatten van -32768 tot +32767
Ushort  - een Unsinged short. Dat betekend dat hij niet onder de 0 kan, dus dit is maximaal +65536
Integer - Kan inhoud bevatten van -2147483648 and +2147483647 (das een helehoop)
uInteger- een Unsinged integer. Die kan dus ook niet onder de 0. Maximaal kan hij dus +4294967296 aan.
float   - Een 4 bytes lang getal dat onder de 0 kan komen.
double  - Zelfde als float, maar dan met nog grotere getallen.
Het meeste wat ik gebruik is Byte, String en Integer.
Ik gebruik byte omdat dat sneller is dan integer. Als ik niet meer dan 255 nodig heb, gebruik ik byte.
Als er een variabel in komt dat de speler zelf kan weizigen, maak ik er uit voorzorg maar integer van.
In een pakketje kan van alles tegelijk zitten. Zoiets:
Code:
/-------Pakketje-------\
|          4    |byte
|        "Rob"         |string
|         32           |integer
|         48           |integer
|         8            |integer
\----------------------/
Dit zegt jou niets, maar je spel wel. Jij kan in je client dit programmeren:
GML:
writebyte(4) //Dit word het ID van het bericht, zodat de server  
             //kan zien wat hij uit het pakketje moet halen.
//In dit geval verzenden wij van alles (zie hieronder).
writestring(""+string(global.playername)+"",true) //Hierin verzenden wij de naam van de speler.
                                                  //In het komende spel verzenden we die maar 1
  //keer, maar dit is even om voor te doen.
                                              //True kan je beter altijd true laten, omdat
  //deze ervoor zorg dat je de lengte van de
  //string meezend. Aangezien wij hier meerdere
  //dingen in 1 pakket verzenden, is dit
  //noodzakelijk.
writeint(x) //Dit is de X positie van de speler
writeint(y) //Hmm... Wat zou dat zijn?
writeint(global.money) //Hier verzenden we de hoeveelheid geld van de speler.
Nu krijg je dus je
[4,"Rob",32,48,8]
verhaal.



Zo kan de server dus makkelijk uitlezen wt de client verzend.
We gaan hier later nog dieper op in.

d. Uitleg over tekens en overige in de text.

Hier even een aantal dingetjes over die in de text voorkomen.

***Iets tussen 3 sterretjes***
Hier staat gewoon even een simpele mededeling in.


Hoofdstuk 2.
Lets Start!

a. Goed Voorbereiden

Nou, wat ik altijd doe is:
Ik maak een standaard gm6 (of gmk-tje) waarin ik allerlij scripts en standaard dingen zet.
Dit gaan we in dit geval ook doen. Hier heb ik het bestand dat je moet hebben:
Download voleddige ZIP
Hierin staat dus:

*  Een GM6-je met allerlij scripts erin
*  De 39dll.dll zelf
*  Deze tutorial
*  Stap voor stap de editables die bij deze tutorial horen,
*  zodat je zeker weet dat je niets fout doet, of geen zin hebt
*  om de code te CTRL+C'en en dan te CTRL+V'en.
*  (je weet wel, de sneltoetsen voor kopieren en plakken ^.^)


Maak nu een nieuw game-maker project.
Merge alvast de gm6 die in de zip zat, dus standaard39dll.gm6.

Nou, dan zijn we er nu klaar voor... Smile


b. De Server Opzetten

De server, daar draait het allemaal om. Deze vergaart en verzend data.
Allereerst, includen we de 39dll.dll. (zie hieronder)
                                 
*  Klik op global game settings (onderin)   
*  Klik op de tab Include             
*  Klik op de Add knop                   
*  Selecteer de 39dll.dll uit de zip     
*  Klik op ok                           
*  Selecteer dan beide vinkjes 
   [] Overwrite existing files
   en
   [] Remove at game end
*  Dan klik je op OK.



Nu zal standaard de 39dll.dll bij je game zitten.
Dit werkt makkelijker Wink

Nou, dan beginnen we met een 'meester' object.
Die maakt global variabelen, laadt de DLL,
je weet wel. Dat soort dingen.
Maak het object 'obj_ubercontroller' aan.
Zet in de game start event dit script:

GML:
dllinit(0,true,true);

Dit is een functie uit de ge-mergde gm6.
Deze laadt de gehele 39dll.dll.
Het eerste argument is de naam van de DLL
Als deze een real is, doet hij de standaard (39dll.dll).
Je kan hier eventueel in de vorm van de string de naam
van je dll zetten. (als je 39dll.dll bijv. hernoemt)
De laatste twee staan voor of ze alle functies van de dll inladen.
In dit geval doe ik ze voor de zekerheid toch maar allebij Wink

Dan maken we een game end event waarin dit stuk code staat:
GML:
dllfree();
show_message("De server is gestopt.");
game_end();

De code dllfree() gooit de gelaadde dll weer uit het geheugen.
De rest spreekt voor zichzelf.

Nou, nu we de standaard hebben, gaan we terug naar de game start event.

Daar bewerken we de oude code, door dit toe te voegen:

GML:
global.tcppoort=12564; //Dit is de poort voor de TCP connectie. Die kan je het beste zo laten.
global.maxplayers=20; //Hier zeggen we alvast hoeveel spelers we maximaal in het spel willen hebben. In dit geval 20 :)
for(i=0; i < global.maxplayers; i+=1)
{
global.players[i] = -1;
}
//dit stukje code zal de array maken voor alle spelers. Kom ik later op terug.


//Hier openen we de TCP connectie door naar inkomende pakketjes te luisteren op poort global.tcppoort.
global.servertcp = tcplisten(global.tcppoort,10,1);
//arg0=De poort waar we naar luisteren
//arg1=Maximum aantal connecties op die poort vanaf deze PC (Kan je beter 10 laten)
//arg2=Blocking(0) mode of Non-Blocking(1) mode. Deze kan je ook het beste 1 laten.

//Nu gaan we kijken of die connectie goed is gegaan. Zoniet, klopt de poort niet.. Of de firewall.
//Of iets anders... :) Zoals dat je PC uit elkaar is geknald.
if (global.servertcp) then {
//De server startte!! :D YAY!
show_message("TCP socket geopend op poort "+string(global.tcppoort)+".");
}
else
{
//Als hij dat niet deed :'( dan sluiten de het luisteren, en stoppen de server.
closesocket(global.servertcp);
show_message("Kon de server niet starten op TCP connectie via poort "+string(global.tcppoort)+"!");
game_end();
exit;
}
global.maxplayers=20 //Hier leg ik later meer over uit.
for(i=0; i < global.maxplayers; i+=1)
{
global.players[i] = -1;
}

Maak nu ff snel een room aan en zet dit object erin.

Run nu je game maar eens. Als het goed is krijg je een firewall waarschuwing Very Happy
Van Norton weet ik dat dat pokkeding gaat zeiken :@
maar van windows zelf weet ik het niet precies.. Maar waarschijnlijk zo'n blokkering opheffen dinges.



(jah, die tijd klopt.. Ik ga zo slapen Smile )
***De gm6 totaan dit gedeelte heet 2bserv.gm6 Download***


Nu gaan we even snel een systeempje maken waarmee we berichten en informatie
van de server snel kunnen uitlezen znder show_message();.
Met de funcie show_message(); word het hele spel stilgelegd, en dus
worden er ook geen pakketjes ontvangen / verzonden.

Eerst maken we even een script (in de script map) die heet
showdatetime
Hierin zet je deze code:
GML:
return string(date_datetime_string(date_current_datetime()))
Dit is eingenlijk om dingen wat te versnellen tijdens het programmeren.
In plaats van string(date_datetime_string(date_current_datetime())) te typen,
typen we nu gewoon
showdatetime()

Dan maken we een script genaamd chat_initlines
Daarin zet je deze code:
GML:
{
  global.maxline = 20; //Deze kan je veranderen in hoe groot je scherm is. Ik houd het op 20
  for (i=0; i<global.maxline; i+=1)
  {
    global.line[i] = '';
    global.color[i] = c_black;
  }
}

Dan een script genaamd chat_addline
Daarin zet je deze code:

GML:
//argument0 = De lijn text
//argument1 = De kleur van de text
{
  for (i=0; i<global.maxline-1; i+=1)
  {
    global.line[i] = global.line[i+1];
    global.color[i] = global.color[i+1];
  }
    global.line[global.maxline-1] = argument0;
    global.color[global.maxline-1] = argument1;
}


Dan maken we het laatste script, genaamd  chat_drawlines
Daarin zet je deze code:
GML:
{
  var xxx,yyy;
  xxx = argument0;
  yyy = argument1;
  draw_set_font(fnt_chat);
  for (i=0; i<global.maxline; i+=1)
  {
    draw_set_color(global.color[i]);
    draw_text(xxx,yyy,global.line[i]);
    yyy += 18;
  }
}

Nu voeg je het font fnt_chat toe.
Lettertype:    Veranda
Lettergrootte: 8
Letteropmaak:  Dikgedrukt

Dat zal wel passen.

Nu gaan we naar de obj_ubercontroller, waarin we in de game start event dit lijntje code toevoegen:
GML:
chat_initlines();

Onthoud dat dit wel bovenaan moet! Dus boven of precies onder de dllinit(); code.

Nu moet je de regel zoeken van de
GML:
show_message("TCP socket geopend op poort "+string(global.tcppoort)+".");
Die vervangen we door:

GML:
chat_addline("TCP socket geopend op poort "+string(global.tcppoort)+".",c_red);


Dan maken we een draw event in de ubercontroller.
Daarin komt dit stukje code:
GML:
chat_drawlines(10,10);

Start nu de server maar eens Smile

***De gm6 totaan dit gedeelte heet 2bserv2.gm6 Download***

Nou, nu gaan we ervoor zorgen dat de server kijkt wanneer iemand een connectie maakt met de server.
Dit doen we door in de step event te kijken naar connecties die gemaakt worden.
Daar heeft men dit script voor ontworpen:
GML:
tcpaccept(tcppoort,true);

Nou gaan we die erin zetten.
Maak je step-event, en zet er een code-blok in.

Daarin zet je:
GML:
var clientsocket, player;
Dit; dat maakt alvast de variabelen aan die we gaan gebruiken.

en dan dit, die naar de verbindingen zoekt:
GML:
clientsocket = tcpaccept(global.servertcp,true);
Deze kijkt dus naar verbindingen die binnenkomen op de tpc connectie die we eerder hebben gemaakt, genaamd global.servertcp, en noemt variabel clientsocket ernaar.

Dan maken we het zo, dat als er geen verbinding word gemaakt, we er ook niets mee proberen te gaan doen.
Dat doen we door dit simpele scriptje:
GML:
if (clientsocket <=0) exit;
die verlaat de step event als er geen connectie is gemaakt.

Dan gaan we alles synchroniseren in de server en de client.
GML:
setsync(clientsocket,0);

Wat we nu dus hebben:

GML:
var clientsocket, player;

clientsocket = tcpaccept(global.servertcp,true);
if (clientsocket <=0) exit;

setsync(clientsocket,0);

Dit is eigenlijk de basis Smile

We denken vooruit, en zeggen dat het eerste wat de speler zal verzenden de gebruikersnaam is.

Oftuwel, we moeten dat pakketje uit elkaar halen.
Dan doen we dus op deze manier:

Eerst accepteren we het gehele pakketje:
GML:
receivemessage(clientsocket);

dan zetten we variabele name naar de string die in het pakketje word verzonden:

GML:
name = readstring();

Nu we dit weten, gaan we kijken of er genoeg ruimte is voor de spelers.
Dat doen we door te kijken hoeveel obj_client objecten er zijn.
Ik zal zometeen uitleggen hoe alles met obj_client werkt.

GML:
//Kijk of er ruimte over is
if (instance_number(obj_client) = global.maxplayers)
{
//En als er jammer genoeg geen ruimte is, sturen wij dit naar de client die probeert te joinen.
clearbuffer();
writebyte(2);
sendmessage(clientsocket);
exit;
}

Dit hele stuk zal ervoor zorgen dat er niet oneindig veel spelers zullen komen, maar maximaal wat jij hebt ingesteld.

Nu gaan we even wat toevoegen in de array van alle spelers:

GML:
//nu gaan we door de speler array zoeken of er een open plek is. In de speler array word het ID van de speler opgeslagen, in combinatie met het ID van de obj_client. (zometeen leg ik daar wat over uit).
for(i=0; i < global.players; i+=1)
{
//Dus, als we een lege (-1) vinden, stoppen we de loop.
if (global.players[i]==-1) break;
}

Nu gaan we de client vertellen dat hij in het spel zit, en gelijk zijn ID versturen.

GML:
//We sturen de speler die joinde het bericht dat hij erin is. Ook gelijk verzenden wij hem zijn ID.
clearbuffer();
writebyte(1);
writebyte(i);
sendmessage(clientsocket); //het bericht is verstuurd.

Nu weet de speler dat hij in het spel zit Smile
Nu gaan we zorgen dat de server dit ook weet.

We hebben hier een aparte, maar overzichtelijke manier voor.
Voor elke speler hebben we obj_client.
Die is dus voor iedereen hetzelfde.
Een speler IS als het ware een obj_client.
Ze verschillen alleen van elkaar door middel van verschillende id's die wij hun geven.
Zo kunnen we van elk de x en y etc. achterhalen Smile

GML:
//We maken de speler, obj_client
player = instance_create(0,0,obj_client);
//we zetten het speler id in de obj_client
player.pid = i;
//Nu geven wij de socket die word gebruikt voor de speler, aan de speler zelf. Dan weten we naar welk ip we pakketjes moeten sturen :)
player.tcp = clientsocket;
//We geven de speler hem zijn eigen gebruikersnaam
player.username = name;
//Dan zetten we de speler zelf in de globale array.
global.players[i] = player;

Nu zit het bij de client en de server goed.
Alleen, moeten we nu nog naar de andere spelers sturen dat iemand heeft gejoined.
Dit doen we door Message ID 3.
Zoals,
1 was   : Je bent in het spel
2 was   : Sorry, de server zit vol
3 is dus: Iemand anders is in het spel gekomen.
^^^^
Dit is allemaal client side, dus voor de client en niet voor de server.
Leg ik later meer over uit.
Nu verder naar het script:
GML:
//We versturen een simpel pakketje naar iedereen met het ID en de username van degene die joinde.
clearbuffer();
writebyte(3);
writebyte(i);
writestring(name,true);
with(obj_client)
{
if (id!=player)
{
sendmessage(tcp);
}
}
Nu weet de rest dat de client er is.
Alleen -> De client weet niet wie er allemaal al in zitten!

Dat doen we op dezelfde manier Smile
GML:
//En dan zeggen we tegen de gene die net is gejoind, wie er allemaal al zijn.
with(obj_client)
{
if (id!=player)
{
clearbuffer();
writebyte(3);
writebyte(pid);
writestring(username,true);
sendmessage(clientsocket);
}
}

Nu gaan we de synchronisatie stoppen, omdat we klaar zijn met de gehele connectie.
Die word van obj_ubercontroller naar obj_client gezet.

GML:
//Zet de synchronisatie tussen server en client uit.
setsync(clientsocket,1);

//Zeg gelijk op de server dat er iemand bij is gekomen :)
chat_addline(name + " has joined.",c_black);

Nu is het hele server-sided script voor obj_ubercontroller klaar.
Als het goed is heb je nu dit:

GML:
var clientsocket, player;

clientsocket = tcpaccept(global.servertcp,true);
if (clientsocket <=0) exit;

setsync(clientsocket,0);

receivemessage(clientsocket);
name = readstring();

//Kijk of er ruimte over is
if (instance_number(obj_client) = global.maxplayers)
{
//En als er jammer genoeg geen ruimte is, sturen wij dit naar de client die probeert te joinen.
clearbuffer();
writebyte(2);
sendmessage(clientsocket);
exit;
}

//nu gaan we door de speler array zoeken of er een open plek is. In de speler array word het ID van de speler opgeslagen, in combinatie met het ID van de obj_client. (zometeen leg ik daar wat over uit).
for(i=0; i < global.players; i+=1)
{
//Dus, als we een lege (-1) vinden, stoppen we de loop.
if (global.players[i]==-1) break;
}

//We sturen de speler die joinde het bericht dat hij erin is. Ook gelijk verzenden wij hem zijn ID.
clearbuffer();
writebyte(1);
writebyte(i);
sendmessage(clientsocket); //het bericht is verstuurd.

//We maken de speler.
player = instance_create(0,0,obj_client);
//We slaan zijn id op in de client...
player.pid = i;
//We zeggen welk ip bij deze speler hoort. Dus waar hij naar moet luisteren / verzenden.
player.tcp = clientsocket;
//We zetten zijn gebruikersnaam in het object
player.username = name;
//En ook in de lijst :)
global.players[i] = player;

//We versturen een simpel pakketje naar iedereen met het ID en de username van degene die joinde.
clearbuffer();
writebyte(3);
writebyte(i);
writestring(name,true);
with(obj_client)
{
if (id!=player)
{
sendmessage(tcp);
}
}

//En dan zeggen we tegen de gene die net is gejoind, wie er allemaal al zijn.
with(obj_client)
{
if (id!=player)
{
clearbuffer();
writebyte(3);
writebyte(pid);
writestring(username,true);
sendmessage(clientsocket);
}
}

//Zet de synchronisatie tussen server en client uit.
setsync(clientsocket,1);

//Zeg gelijk op de server dat er iemand bij is gekomen :)
chat_addline(name + " has joined.",c_black);


***De gm6 totaan dit gedeelte heet 2bserv3.gm6 Download***

Oke, nu gaan we beginnen met obj_client.
Dus, we beginnen met een object aan te maken.
Noem deze obj_client.

Zet alvast maar in de destroy event dit neer:
GML:
//Stop het socket
closesocket(tcp);

Het is toch maar zo'n klein beetje Wink
Het spreekt voor zichzelf denk ik Smile

Dan gaan we beginnen met het stuk code waar het allemaal om draait.

Maak maar alvast een step event aan bij obj_client.

We beginnen natuurlijk weer met aanmaken van variabelen.
GML:
//Maak de variabelen aan die we gaan gebruiken
var messagesize, messageid;

Daarna, gaan we kijken welke berichten er binnenkomen.
obj_ubercontroller zal alleen berichten met id 1 eruit filteren, dus gaan we hier kijken naar berichten met id 2 of hoger, aangezien 1 het hier toch nooit maakt.

GML:
//Kijk of er een pakketje binnenkomt
messagesize = receivemessage(tcp);
//Als er geen bericht is binnengekomen, stoppen we met dit scriptje.
if (messagesize <=0) break;
//En als er dus wel 1 is, gaan we kijken wt voor bericht er is binnen gekomen.
messageid = readbyte();
Dit spreekt weer voor zichzelf.
Hij kijkt of er pakketjes zijn gekomen,
zonee -> stoppen we met deze hele hap
zoja  -> Gaan we door, en kijken we gelijk wat voor pakketje werd verzonden, door dus het id uit te lezen.

Dit uitlezen doen we met een switch en cases.
Dit gaat zo:
GML:
//We beginnen met case 2, omdat obj_ubercontroller case 1 er al uit haalt.
switch(messageid)
{   
    case 2:
    //Dit is bericht id 2.
    //Deze leest zegmaar de belangrijkste dingen van de client, zoals de x, y en de direction
    var player, value;
    value = readbyte();
    //Nu weten we welk ID welke speler heeft, en gaan we de ontvangen data aan hem koppelen.
    player = global.players[value];
    player.x = readshort();
    player.y = readshort();
    player.direction = readshort();
    clearbuffer();
    //Nu gooien we de buffer weg, en maken ons klaar om dit pakketje 'door te sturen' naar de andere clients.
    writebyte(5);
    writebyte(value);
    writeshort(player.x);
    writeshort(player.y);
    writeshort(player.direction);
    with(obj_client)
    {
    sendmessage(tcp);
    }
    break;
   
    case 3:
    //bericht id 3, iemand is weggegaan. Wat vervelend!
    var playerid, username;
    playerid = readbyte();
    username = readstring();
    clearbuffer();
    //We sturen het weer door naar de anderen.
    writebyte(6);
    writebyte(playerid);
    writestring(username,true);
    with(obj_client)
    {
    sendmessage(tcp);
    }
    chat_addline(username + " heeft het spel verlaten.",c_black);
    with(global.players[playerid])
    {
    instance_destroy();
    //En we vernietigen de speler. Als dit niet gebeurt zal hij alleen maar stil blijven staan. Toevallig is dit dan ook zin 666 :)
    //(mensen die veel Men of War spelen kennen die bug in Men of War wel ;)
    }
    global.players[playerid] = -1;
    //We maken de spelerlijst weer vrij voor andere spelers.
    break;
   
    case 4:
    //:O we kregen een chat bericht van iemand binnen
    chatmessage = readstring();
    chat_addline(chatmessage,c_black);
    clearbuffer();
    writebyte(8)
    writestring(chatmessage,true);
    with(obj_client)
    {
        sendmessage(tcp);
    }
    break;
}
Het is even een hele lap text, maar ik zal hem even uitleggen:

We kijken welk id we binnen krijgen voor het pakketje.

Bijvoorbeeld, 2.
Dan gaan we naar case 2, waarin dit staat:

GML:
 case 2:
    //Dit is bericht id 2.
    //Deze leest zegmaar de belangrijkste dingen van de client, zoals de x, y en de direction
    var player, value;
    value = readbyte();
    //Nu weten we welk ID welke speler heeft, en gaan we de ontvangen data aan hem koppelen.
    player = global.players[value];
    player.x = readshort();
    player.y = readshort();
    player.direction = readshort();
    clearbuffer();
    //Nu gooien we de buffer weg, en maken ons klaar om dit pakketje 'door te sturen' naar de andere clients.
    writebyte(5);
    writebyte(value);
    writeshort(player.x);
    writeshort(player.y);
    writeshort(player.direction);
    with(obj_client)
    {
    sendmessage(tcp);
    }
    break;
Hier plukken we het pakketje uit elkaar, gebruiken de data, en sturen hem weer door.
We krijgen dus de x, y en direction binnen van een speler.
Die geven we aan obj_client, (dus zichzelf) en dan sturen we hem door naar de andere clients Smile (die smiley was toevallig net letter 22222 Razz)

Nu werkt eigenlijk onze hele server al! Very Happy
Zo simpel is dat!

Alleen: we gaan nog ff maken dat we kunnen chatten.
Zegmaar dat de server berichtjes kan sturen naar de spelers.
Dat gaat erg makkelijk, namelijk zo:

In de draw event van obj_ubercontroller zetten we dit erbij:
GML:
draw_set_color(c_black)
draw_text(10,430,"> " + string(keyboard_string) + "|");

in totaal staat er in de draw event dan
GML:
chat_drawlines(10,10);
draw_set_color(c_black)
draw_text(10,430,"> " + string(keyboard_string) + "|");

En we maken een KeyBoard Press enter event.
Dus zo eentje met een Rode pijl Smile

Daarin zetten we een stuk code, namelijk:
GML:
//Eers kijken we of de server wel wat heeft ingetypt :P
if keyboard_string != ""
{
//Zoja, zetten we het chatbericht in de server zelf, zodat hij kan zien dat hij iets heeft verzonden.
chat_addline(keyboard_string,c_black);
//Dan gaan we het bericht verzenden naar de clients. We doen dit met bericht id 9, omdat de rest al in gebruik is.
clearbuffer();
writebyte(9);
writestring(keyboard_string,true);
with(obj_client)
{
    sendmessage(tcp);
}
//En we maken de keyboard string weer leeg.
keyboard_string = "";
}

n, nu we clients hebben, moeten we aan hun vertellen dat de server stopt als we hem afsluiten.
Oftuwel, aan de game end van obj_ubercontroller, voeg je AAN DE BOVENKANT TOE:
GML:
//We vertellen tegen elke client dat we afsluiten.
clearbuffer();
writebyte(7);
with(obj_client)
{
sendmessage(tcp);
}

//Stop de socket
closesocket(global.servertcp);
zodat we nu hebben:

GML:
//We vertellen tegen elke client dat we afsluiten.
clearbuffer();
writebyte(7);
with(obj_client)
{
sendmessage(tcp);
}

//Stop de socket
closesocket(servertcp);

dllfree();
show_message("De server is gestopt.");
game_end();

Nou, dan is de server eigenlijk af Very Happy



Wat we nu gedaan hebben:
We maakten een server die een poort voor zichzelf openzet, waardoor hij kijkt naar nieuwe connecties.
Als hij een connectie ziet, nemen we op wat er in het pakketje staat.
Daarna maken we de speler op de server, en laten hem communiceren met de client.
En de server kan nu chat berichten sturen naar de clients.

Dat was dat! Very Happy

***De gm6 tot nu toe heet: Server.gm6 Download***

c. De Client Opzetten

Nou, nu gaan we beginnen met de client.
Dit is het meeste werk, omdat we hier het spel zelf in moeten zetten n het spel moeten maken.
Laten we beginnen met een simpel spelletje.
We maken een poppetje dat topdown kan rondlopen, en met muren waar hij niet doorheen kan.

Ik ga dit niet helemaal bespreken, omdat ik weet (eigenlijk hoop) dat je dit wel kan...

Ik heb ff snel een gm6-je voor je gemaakt.

***De gm6 totaan dit gedeelte heet 2cclient.gm6 Download***

Nu gaan we beginnen met het spel online te maken.
Laten we weer, net als bij de server, beginnen met de standaard39dll.gm6 te mergen in het spel.

Nu maken we even snel alvast het chat-gedeelte voor de client. Werkt ongeveer het zelfde als de server maar niet helemaal.
Maak een font genaamd
fnt_chat
Daarin stellen we het volgende in:
Lettertype: Veranda
Lettergrootte: 8
Vetgedrukt

We maken nu even een script, genaamd
chat_initlines
daarin zet je deze code:
GML:
{
  global.maxline = 6; //Dit is nu 6. Een stuk korter, maar anders zit het door je hele spel heen.
  for (i=0; i<global.maxline; i+=1)
  {
    global.line[i] = '';
    global.color[i] = c_black;
  }
}

En je maakt een script genaamd
chat_addline

daarin zetten we dit:
GML:
//We voegen een stuk text toe aan de chat-dinges.
//argument0 = de text, argument1 = de kleur
{
  for (i=0; i<global.maxline-1; i+=1)
  {
    global.line[i] = global.line[i+1];
    global.color[i] = global.color[i+1];
  }
    global.line[global.maxline-1] = argument0;
    global.color[global.maxline-1] = argument1;
}

Dan, ten slotte nog 1 script, en dat noem je
chat_drawlines
Daarin komt dit:
GML:
//Drawt de chat-text op een bepaalde plek.
var colour;
colour = draw_get_color()
{
  var xxx,yyy;
  xxx = argument0;
  yyy = argument1;
  draw_set_font(fnt_chat);
  draw_set_halign(fa_left);
  for (i=0; i<global.maxline; i+=1)
  {
    draw_set_color(global.color[i]);
    draw_text(xxx,yyy,global.line[i]);
    yyy += 20;
  }
}
draw_set_color(colour)

Dan gaan we weer de tcp-poorten en connecties openen.

We maken een object, genaamd obj_ubercontroller.
We voegen er een game-start event aan toe.
Daarin zetten we een stuk code.
Dat gaat als volgt:
GML:
//We laden de DLL
dllinit(0,true,false);

//We laden net als bij de server het chat-gedoe
chat_initlines();

//We vragen nu om de gebruikersnaam van de speler.
global.username = get_string("Gebruikersnaam:","");

//En we vragen het IP dat de speler wilt joinen.
//Je kan dit ook zelf instellen.
//En, 127.0.0.1 f localhost staat voor de lokale computer.
//Dus als je de server op deze pc draait en het spel ook, moet je ip 127.0.0.1 of localhost invoeren.
global.serverip = get_string("Server IP:","127.0.0.1");

//Hier stellen we de poort in.
//Moet hetzelfde zijn als bij de server! Anders lukt het niet!
global.serverport = 12564;

//Dit is erg belangrijk, dat het hetzelfde is als op de server.
//In elk geval niet kleiner! Anders loopt serieus ALLES door elkaar en gaat het ALLEMAAL fout. Bij de server en dus ook bij alle ander clients.
global.maxplayers=20;

Nu hebben we alles ingesteld. Nu gaan we maken dat de client een connectie
maakt met de server global.serverip via poort global.serverport
via het protocol TCP/IP.

GML:
//Hier leggen we de connectie... Althans, dat proberen we
global.clienttcp = tcpconnect(global.serverip,global.serverport,1);

//Want hier kijken we of alles goed is.
if (!tcpconnected(global.clienttcp))//Als hij goed is, geeft die functie FALSE terug.
    {
    //En is dat niet het geval, bijv. de server blokkeert deze verbinding, zeggen we dat het niet lukte.
    show_message("Kon geen verbinding maken met de server.");
game_end();
//en stoppen we het spel :)
    }
else
    {
    //En als dit niet het geval is, vertellen we dit aan de client.
    setnagle(global.clienttcp,1);
    chat_addline("Er is nu verbinging met "+string(global.serverip)+":"+string(global.serverport)+" via TCP/IP.",c_green);
   
    //Dan sturen we onze gebruikersnaam naar de server :)
    clearbuffer();
    writestring(global.username,true);
    sendmessage(global.clienttcp);
    }

Dan gaan we de rest nog even doen, zoals de spelerlijst aanmaken.
GML:
//We maken de spelerlijst
var i;
for(i=0;i<global.maxplayers;i+=1)
    {
    players[i] = -1;
    }

//We geven de speler een hartelijk welkom.
chat_addline(global.username+", hoi.",c_black);

//Beginnen met de ping te maken
deltatime = current_time;
ping = 0;

//Dit is de timeout-tijd.
//Dus als de speler voor langer dan 8 seconde geen reactie krijgt van de server, stopt het spel.
//Dit is in milliseconden.
timeout = 8000;
send=0
Maak een simpele room met n speler en n obj_ubercontroller. En misschien vool de lol wat muurtjes Smile

Start hem nu maar eens!

HET WERKT!
Je hebt zojuist VIA TCP/IP de server laten weten dat er een speler heeft gejoint!
Very Happy

***De gm6 totaan dit gedeelte heet 2cclient2.gm6 Download***

Alleen, je kan nog niet chatten, elkaar nog niet zien, en de server merkt het niet eens als je weggaat!
Dus eigenlijk gaat nu alles fout.
Daar gaan we wat aan doen Smile

Maak bij obj_ubercontroller een draw event aan.
Zet daarin deze code:
GML:
//Zet de kleur alvast naar zwart
draw_set_color(c_black);

//Nu drawen we de chat :)
chat_drawlines(view_xview[0]+16,view_yview[0]+16);

//En nu de text, voor het chatten.
if send = true
    {
    draw_set_alpha(0.5)
    draw_set_color(c_black)
    draw_roundrect(view_xview[0]+16-2,view_yview[0]+144-2,view_xview[0]+16+string_width(keyboard_string)+5,view_yview[0]+144+15,0)
    draw_set_alpha(1)
    draw_set_color(c_white)
    draw_text(view_xview[0]+16,view_yview[0]+144,keyboard_string);
    draw_set_color(c_black)
    }
Dan hebben we dat..
Wat we nu nog even gaan doen, is de ping, de FPS en de gebruikersnamen boven de spelers.
We hebben al in ons hoofd dat het object voor de andere speler obj_player_other word.

Voeg bij het draw event dit toe:
GML:
//We drawen de ping
draw_text(view_xview[0]+544,view_yview[0]+16,"Ping: " + string(ping) + "ms");

//We drawen de FPS. (altijd handig :) )
draw_text(view_xview[0]+466,view_yview[0]+16,"FPS: " + string(fps));

//We drawen de gebruikersnaam boven de speler zelf.
draw_set_halign(fa_center);
draw_text(obj_speler.x-15,obj_speler.y-25,global.username);

//En we drawen de gebruikersnaam van een andere speler boven hem.
with(obj_player_other)
    {
    draw_text(self.x-15,self.y-25,username);
    }

Dan was dat de draw event Smile

Nu over naar het belangrijkste gedeelte. (lol dit is toevallig zin 1000 Razz )
De pakketjes!
We krijgen dus allerlij pakketjes van de server naar ons toegestuurd.
Dit met de message id's.
Die gaan we hier uit elkaar plukken.

Dat gaat als volgt.
Maak een step event aan in de obj_ubercontroller.
Zet daarin een script.
In dat script zetten we dit stukje code:

GML:

//We maken alvast geheugen vrij voor onze variabelen.
var messagesize, messageid;

//Nu gaan we kijken naar berichten die binnen komen.
while(1)
    {
    //We kijken of er een berichtje is.
    messagesize = receivemessage(global.clienttcp);
   
    //Als er geen bericht was, stoppen we met dit stuk code. (alles wat tussen de { en de } staat van de while(1).
    if (messagesize <= 0) break;
   
    //Als er wel een bericht binnenkwam, kijken we wat het bericht is.
    messageid = readbyte();
   
    //Nu zetten we de ping naar 0 omdat we een bericht van de server kregen.
    ping = 0;
   
    //En we gaan kijken wat de server ons probeert te verzenden.
    switch(messageid)
        {
        //Als het bericht id 1 is, doen we dit:
        case 1:
        //We slaan ons id op als het ID wat we van de server krijgen.
        global.myid = readbyte();
        obj_speler.alarm[0] = 1;
//Dit is het alarm van de speler.
//In dit alarm word elke keer de X en de Y en de andere dingen van de speler verzonden.
//Dit doe ik niet in de step van de speler, maar met een alarm, zodat we de interval-tijd kunnen beperken.
//Oftuwel, we hoeven niet elke keer de x en y te updaten. Dit kan ook wel eens per kwart seconde. Het ziet er haperender uit,
//maar er word vele malen minder data verzonden van en naar de server.

        //En we stoppen de switch.
        break;
   
        case 2:
        //Dit bericht krijgen we als de server vol is.
        show_message("Sorry! De server zit vol.");
        stop_spel_online();
//Kom ik zometeen op terug.
        break;
       
        case 3:
        //Er is een nieuwe speler. (het object maken)
        var userid, name, user;
        userid = readbyte();
        user = instance_create(obj_speler.xstart,obj_speler.ystart,obj_player_other);
        user.pid = userid;
        name = readstring();
        user.username = name;
        players[userid] = user;
        chat_addline(name + " is ingelogd.",c_black);
        break;
       
        case 4:
        //Er is een nieuwe speler. (het chat bericht)
        var name, userid;
        userid = readbyte();
        name = readstring();
        user = players[userid];
        user.username = name;
        chat_addline(name + " is ingelogd.",c_black);
        break;
       
        case 5:
        //Nu krijgen we de data van andere spelers binnen.
//Dit slaan we op in het corresponderende object van de client.
        value = readbyte();
        player = players[value];
        player.x = readshort();
        player.y = readshort();
        player.direction = readshort();
        break;
       
        case 6:
        //een speler verlaatte het spel.
        var playerid, username;
        playerid = readbyte();
        username = readstring();
        chat_addline(username + " is uitgelogd.",c_black);
        with(players[playerid])
            {
            instance_destroy();
            }
        players[playerid] = -1;
        break;
       
        case 7:
        //De server kapt ermee
        show_message("De server is gestopt.");
        game_end();
        break;
       
        case 8:
        //Een chat bericht
        chat_addline(readstring(),c_black);
        break;
       
        case 9:
        //Een chat bericht van de sevrer
        chat_addline("Server: " + readstring(),c_red);
        break;
        }
    }

//Als we geen bericht van de server kregen maken we de ping hoger.
ping += current_time - deltatime;
deltatime = current_time;
//Kijk of we ge timed-out zijn, bijv. als er geen verbinding meer is met de server.
if(ping > timeout)
{
show_message("Je bent ge-timed out.");
game_end();
}
Nou, nu zijn we grotendeels wel klaar Smile
Alleen, je zag een script dat we nog niet hebben gemaakt.
Dit gaan we nu even doen Smile
Maak een script aan genaamd
stop_spel_online
Zet daarin:
GML:
//Zeg tegen de server dat we weg zijn.
clearbuffer();
writebyte(3);
writebyte(global.myid);
writestring(global.username,true);
sendmessage(global.clienttcp);
show_message("Je hebt de server verlaten.");

//We stoppen de connectie en legen het geheugen van de DLL
closesocket(global.clienttcp);
dllfree();
//En we stoppen het spel.
game_end();
Dit zorgt ervoor dat als je spel ermee kapt, de server dat weet.

We gaan nu het chat-systeempje in de client bouwen.

Dat doen we als volgt:
Maak een event in obj_ubercontroller.
De key press enter event. (met het rode pijltje)

Zet daarin dit stukje code:
GML:
if send = true
    {
    //Kijk of de speler wel wat heeft ingetypt als hij wat wilt verzenden.
    if keyboard_string != ""
        {
        //Zoja, verzenden wij zijn bericht.
        clearbuffer();
        writebyte(4);
        writestring(global.username + ": " + keyboard_string,c_red);
        sendmessage(global.clienttcp);
        keyboard_string = "";
        }
    send = false
    exit;
    }
else
    {
    keyboard_string = ""
    send = true
    exit;
    }

Dan zijn we eigenlijk wel klaar met de ubercontroller...
Alleen nog een klein dingetje in obj_ubercontroller:
in de game_end event:
execute a script
en doe dan het script stop_spel_online.

Nu gaan we nog even snel alarm 0 in de speler instellen.
open object obj_speler en maak alarm 0 aan.
Zet daarin deze code:
GML:
//We sturen onze positie
clearbuffer();
writebyte(2);
writebyte(global.myid);
writeshort(x);
writeshort(y);
writeshort(direction);
sendmessage(global.clienttcp);

//En herhaal het :)
alarm[0] = 1;

Nu moeten we alleen nog obj_player_other maken.

Dit word erg gemakkelijk, omdat deze eigenlijk al volledig word bestuurd door de ubercontroller.

Maak dus nu het object genaamt obj_player_other aan.

Geef deze de sprite die hetzelfde is als die van je speler.
(in mijn geval het beertje Smile )

En dan...

Ben je klaar Razz

***De gm6 totaan dit gedeelte heet client.gm6 Download***


Nou, dan heb je nu je spel online gekregen, en afgemaakt!
Ik hoop dat je er wat van hebt geleerd Smile
En als je een spel afmaakt, waarbij deze tutorial je heeft geholpen zou ik hem graag zien Very Happy

En opbouwende kritiek is altijd handig Smile

Bedankt voor het lezen,

Groetjes, Rob Quist.

Woorden: 5869
Letters: 39647
Uren: Hmm... ik denk 4 of 5 uurtjes?