Slik kodes du et selvstendig PHP / SQL-leaderboard for ditt spill

I denne artikkelen skal vi lage vårt første MySQL leaderboard for å være vert for et nettsted eller en webserver ved hjelp av enkle PHP og noen SQL. Vi vil da lage et enkelt Unity eksempel i C # bruker GUIText objekter for å legge til nye poeng til vårt toppliste, vise de ti toppene og vise en brukers poengsum og rangering.


Introduksjon

Enkeltspiller spill er morsomme, men å slå din egen highscore kan bli kjedelig. Hvis du legger til et leaderboard i spillet, får du virkelig motivasjon for spillerne til å forbedre sine poeng og spille spillet mer, og det kan til og med brukes til å finne ut om spillet ditt er for lett eller hardt. I spill som fortsetter for alltid, kan leaderboards være den eneste grunnen til at spillerne dine spiller. Hvis du har din egen nettside eller server, vil du kanskje være vert for ditt eget leaderboard, så du har full kontroll over spillet ditt.


Opprette ditt leaderboard

Først av alt må du ha en SQL-database på serveren eller nettstedet ditt. Nettsteder kommer ofte med en innebygd MySQL-database. Detaljer om dette vil variere avhengig av hvilken tjeneste du bruker, men du bør kunne finne din SQL-verts, brukernavn og passord (samt databasens navn) fra administrasjonspanelet eller registreringsmeldingen.

I dette eksemplet brukes phpMyAdmin for å få tilgang til databasen (bygget rett inn i administrasjonspanelet). Du vil åpne databasen og åpne SQL-fanen. Hvis du har mer kontroll over serveren din, kan du opprette en ny database.

Deretter legger du inn følgende SQL:

 CREATE TABLE Scores (navn VARCHAR (10) IKKE NULL DEFAULT 'Anonym' PRIMARY KEY, score INT (5) UTSIGNERT IKKE NULL DEFAULT '0', ts TIMESTAMP IKKE NULL DEFAULT CURRENT_TIMESTAMP) ENGINE = InnoDB;

Dette vil skape et bord med tre variabler:

  • Navn, som inneholder brukernavnene dine, og som lagrer 10 tegn. Dette er vårt bords hovedidentifikator, så det betyr at det kun kan lagre én rad per brukernavn.
  • poengsum, som holder hver brukers høyeste poengsum. I dette eksemplet er det en usignert variabel, så det kan bare være positivt. Hvis du vil ha negative poeng, må du endre det.
  • ts, et tidsstempel vi kan bruke til å endre rekkefølgen på vårt toppliste.

Nå, hvis du bruker SQL Server og ikke MySQL, kan du fortsatt bruke TIMESTAMP, men for verdien må du bruke GetDate () i stedet for CURRENT_TIMESTAMP.

En ekstra ting å huske på: Hvis du lager et veldig enkelt spill, vil du kanskje ikke knytte poeng til navn (for å tillate at hver spiller har flere poeng i topp 10, for eksempel). Dette kan være en dårlig ide skjønt; du kan ha en spiller som er så god at de kan dominere hele topp 10! I dette tilfellet vil du ikke ha det Navn som en primærnøkkel, og du vil også legge til dette:

 ID INT (8) UTSIGNERT IKKE NULL AUTO_INCREMENT PRIMARY KEY

Dette vil sørge for at en merkbar ny rad blir lagt til for hver score.

Klikk og du er ferdig! Ditt bord er alt klart nå.


Sette opp PHP-filene dine

Nå må du lage noen PHP-filer. Dette er de midtre mannene i operasjonen, og gir en måte for Unity å få tilgang til serveren din. Den første PHP-filen du trenger, er AddScore.php. Du må kjenne serverinformasjonen fra før.

  

(Erstatte SQLHOST, SQLUSER, SQLPASSWORD og YOURDATABASE med din egen informasjon.)

Her har vi nettopp prøvd å koble til databasen. Hvis tilkoblingen mislykkes, vil Unity bli informert om at forespørselen mislyktes. Nå skal du sende informasjon til serveren:

 $ brukernavn = mysql_real_escape_string ($ _ GET ['navn'], $ db); $ score = mysql_real_escape_string ($ _ GET ['score'], $ db); $ hash = $ _GET ['hash']; $ Privat = "ADDYOURKEY";

The hash brukes til å kryptere dataene dine og stoppe folk fra hacking på ditt leaderboard. Det blir generert med en skjult nøkkel i Unity og her, og hvis de to hashene passer, får du tilgang til databasen din.

 $ expected_hash = md5 ($ brukernavn. $ score. $ privateKey); hvis ($ expected_hash == $ hash) 

Her genererer vi hash oss selv og kontrollerer at hash vi sender fra Unity er identisk med hash vi forventer. Hvis det er, kan vi sende forespørselen vår!

 $ query = "INSERT INTO Scores SET-navn = '$ name', score = '$ score', ts = CURRENT_TIMESTAMP

Dette er første halvdel av SQL-spørringen. Det skal være ganske selvforklarende; Innleverte poeng og brukernavn legges til bordet, og tidsstempelet oppdateres. Den andre halvdelen er mer komplisert:

 PÅ DUPLIKATE KEY UPDATE ts = hvis ('$ score'> score, CURRENT_TIMESTAMP, ts), score = hvis ('$ score'> score, '$ score', score); ";

Vi kontrollerer først om brukernavnet (vår primære nøkkel) allerede har en rad. Hvis det gjøres, i stedet for å sette inn en ny oppføring, blir oppføringen oppdatert. Vi vil oppdatere tidsstempel og score, men bare hvis den nye poengsummen er høyere!

Ved hjelp av hvis uttalelser, vi sørger for at de nye verdiene bare brukes hvis den nye poengsummen er større enn dagens poeng, ellers brukes de opprinnelige verdiene.

 $ result = mysql_query ($ query) eller die ('Query failed:'. mysql_error ()); ?>

Til slutt løper vi spørringen vår og lukker vår PHP.

Denne filen går på vår server. Du må huske nettadressen. På samme måte må vi lage to andre PHP-filer med forskjellige søk, som vi vil ringe TopScores.php og GetRank.php.

De TopScores spørringen er rett og slett:

 SELECT * FROM Scores ORDER ved score DESC, ts ASC LIMIT 10

Dette vil ta de 10 beste verdiene basert på poengsummen, og for bundet lag setter spillere som har fått poengsummen først lengst oppe på bordet. Denne gangen ønsker vi å trekke ut data også, så legger vi også til:

 $ result_length = mysql_num_rows ($ resultat); for ($ i = 0; $ i < $result_length; $i++)  $row = mysql_fetch_array($result); echo $row['name'] . "\t" . $row['score'] . "\n"; 

Dette vil trekke ut våre resultater og tabulere dem på en måte som vi kan sette dem inn i arrays i Unity.

Endelig har vi GrabRank:

 SELECT uo. *, (SELECT COUNT (*) FRA Scorer ui WHERE (ui.score, -ui.ts)> = (uo.score, -uo.ts)) AS rang FRA Scores uo WHERE navn = '$ navn' ;

Dette vil gi oss vår spillers rangering i resultattavlen. Vi kan da pakke ut det ved å ekko $ Rad [ 'rang'].

Vår kildekode inneholder også en sanitiser-funksjon, som forhindrer brukere i å legge inn sverdetord på topplisten eller forsøke et SQL-injeksjonsangrep.


Å lage et enkelt minigame i enhet

Nå trenger vi et spill å bruke vår highscore bord med! Vi skal bare teste hvor mange klikk hver bruker kan gjøre innen ti sekunder, men du kan legge til topplisten til et hvilket som helst spill.

Oppsett

Vi starter med å lage fire GUIText objekter. Disse bør være forankret om midt senter for enkelhets skyld. Du kan justere disse med pikselforskyvning for å få dem på riktig sted, men hvis du vil at de skal justere posisjonen for enhver oppløsning, er det enklere å endre X og Y posisjon (mellom 0 og 1); Ellers må du justere dem ved oppstart.

Du må imidlertid justere skriftstørrelsen ved oppstart hvis du vil kjøre i alle oppløsninger. En rask måte å gjøre dette på er å basere dem på skjermens høyde. Vi kan gjøre dette ved å lage en klasse som gjør dette og feste det til alle våre tekstobjekter, men det er mye enklere å gjøre alt dette fra en klasse.

Det spiller ingen rolle hva objektet vi velger som vår "manager", så vi kan bare sette denne klassen på våre klikkteller. Så i vår første klasse skriver vi:

 void Start () foreach (GUIText chosentext i FindObjectsOfType (typeof (GUIText)) som GUIText []) chosentext.blah.fontSize = Mathf.FloorToInt (Screen.height * 0.08f); 

Dette vil finne hver tekstobjekt i scenen og skalere den til en fornuftig størrelse.

Nå ønsker vi at klikktelleren skal være større enn den andre teksten, så hvis vi holder denne klassen der, har vi den ekstra bonusen at vi også kan sjekke om guiText i spørsmålet er den tilknyttet dette GameObject:

 hvis (blah == guiText) blah.fontSize = Mathf.FloorToInt (Screen.height * 0.18f); ellers [etc.]

gameplay

Klikkkomponenten i spillet vil være veldig enkelt. I starten ønsker vi ikke at timeren skal telle ned til første klikk, så vi skal lage to private bools i vår klasse - firstClick og allowedToClick. I Start() vi kan sette firstClick til falsk og allowedToClick til ekte.

Nå trenger vi disken til å faktisk registrere klikkene, og det er et par måter å gjøre dette på. Vi kunne holde en heltallvariabel som sporer poenget, eller vi kunne gjøre det litt mindre effektivt, men i en linje (og med noe så enkelt vi ikke trenger å optimalisere, men det er god praksis). Så vi registrerer klikket i Oppdater() funksjon, og øk verdien ved å lese strengen.

 ugyldig oppdatering () if (allowedToClick && Input.GetMouseButtonUp (0)) hvis (! firstClick) firstClick = true; StartCoroutine (nedtellings ());  guiText.text = (System.Int32.Parse (guiText.text) + 1) .ToString (); 

Som du kan se her, oppnås inkrementet ved å lese strengen som et heltall, legge til en, og deretter konvertere tilbake til en streng. Du vil også se her at vi har kjørt en coroutine så snart brukeren klikker først, som starter nedtellingen.

Vi bruker rekursjon i denne funksjonen. Igjen kan vi bruke et heltall som holder nedtellingen verdi for effektivitet, men vi vil bruke streng manipulering igjen.

 IEnumerator Countdown () yield returnere nye WaitForSeconds (1); counter.guiText.text = (System.Int32.Parse (counter.guiText.text) - 1) .ToString (); if (counter.guiText.text! = "0") StartCoroutine (Countdown ());  else allowedToClick = false; GetComponent() .Setscore (System.Int32.Parse (guiText.text)); toptext.guiText.text = "Skriv inn brukernavnet ditt."; GetComponent() .enabled = true; 

Merk: Det er viktig at vi brukte StartCoroutine () og kalte ikke bare denne funksjonen fordi den er en IEnumerator. De utbytte uttalelse får det til å vente et sekund før noen tiltak blir tatt. Den fjerner en fra disken, og hvis verdien ikke er null, kalles den igjen. På denne måten teller funksjonen til den når 0.

Navn Oppføring

Etter dette stopper brukeren å klikke, spør etter brukernavnet, og får tilgang til våre andre og tredje klasser (som vi skal skrive!). Vi tar en titt på hva disse gjør nå, fra og med NameEnter.

I NameEnter () Vi skal tillate en bruker å skrive inn brukernavnet sitt, med noen begrensninger. I utgangspunktet ønsker vi å vise understrekkskarakteren _, som blir slettet så snart de begynner å skrive navnet sitt. På toppen av dette ønsker vi ikke at de skal kunne bruke tegn som \ eller ', som disse ville ødelegge våre SQL spørringer.

Vi skal bruke en strengbygger for å lage dette. Først plasserer vi noen variabler øverst i vår klasse:

 privat int MaxNameLength = 10; privat StringBuilder playerName; privat bool backspacepossible; privat bool startpress;

De MaxNameLength bør settes på samme lengde som du brukte for din VARCHAR lengde når du lagde bordet ditt. Her har vi vår strengbygger, spillernavn, og to booleans. Den første, backspacepossible, er å kontrollere brukerens evne til å holde nede bakrommet for å slette tegn. Det andre er å angi om de har begynt å skrive navnet sitt enda.

I Start(), vi må ta vare på noen få ting. Vi deaktiverer all tekst bortsett fra den som heter Topp tekst; vi kan gjøre det i en for hver sløyfe, som før.

 void Start () foreach (GUIText tekst i FindObjectsOfType (typeof (GUIText)) som GUIText []) if (text.name! = "Toptext") text.guiText.enabled = false;  GetComponent() .enabled = false; playerNameTemp = new StringBuilder (); playerNameTemp.Append ( "_"); backspacepossible = true; initialpress = false; 

Her kan du se at vi har gjort noen ting. Vi har deaktivert vår første klasse (ClickTimes) siden vi ikke bruker det lenger. Vi har også opprettet en forekomst av playerNameTemp og vedlagt den med _, så spillerne kan se hvor navnet deres går, og vi har initialisert våre variabler.

Nå må vi la spilleren faktisk skrive inn navnet sitt. Ved slutten av Oppdater() vi plasserer følgende utdrag:

 guiText.text = playerNameTemp.ToString ()

Dette vil sørge for at teksten viser hva vår strengbygger registrerer.

Deretter håndterer vi tegninngang:

 hvis (playerNameTemp.Length < MaxNameLength)  foreach (char c in Input.inputString)  if (char.IsLetterOrDigit(c) || c == '_' || c ==")  if (!initialpress)  initialpress = true; playerNameTemp.Remove(0, 1);  playerNameTemp.Append(c);   

Så, forutsatt at strengbyggerens lengde er mindre enn den maksimale navnlengden, og så lenge brukeren skriver inn tegn som er enten bokstaver, siffer, mellomrom eller understreker (selv om du kanskje bare velger å tillate alfanumeriske tegn), vil strengen være vedlagt det nye sifferet. I tilfelle at dette er det første trykket, vil den opprinnelige underskriften fjernes før det nye brevet er lagt til.

Neste:

 hvis (playerNameTemp.Length> 0) if (Input.GetKeyDown (KeyCode.Backspace)) hvis (! startpress) initialpress = true;  backspacepossible = false; StartCoroutine (BackspaceInitialHold ()); playerNameTemp.Remove (playerNameTemp.Length - 1, 1);  annet hvis (backspacepossible && Input.GetKey (KeyCode.Backspace)) backspacepossible = false; StartCoroutine (BackspaceConstantHold ()); playerNameTemp.Remove (playerNameTemp.Length - 1, 1);

Så lenge det ikke er noen tegn igjen i vår strengerbygger, og det er mulig å bruke backspace, kan brukeren fjerne tegn. Legg merke til forskjellen mellom første og andre setninger. Den tidligere bruker GetKeyDown (), mens sistnevnte bruker Getkey () (og sjekker vår bool). Sondringen er at vi bør slette et tegn hver gang brukeren trykker på backspace, men ikke konstant mens brukeren holder den nede.

The coroutines BackspaceInitialHold () og () bare vent 0,15 og 0.05 sekunder, henholdsvis, og sett deretter backspacepossible til ekte. Så etter at brukeren har holdt nede på backspace for 0,15 sekunder, så lenge de fortsatt holder tilbake plass, slettes et tegn hver gang 0.05 sekunder (så lenge som lengde er større enn kode> 0).

Vi fastsetter også at hvis dette er den første knappen, trykker brukeren, initialpress utløses (så det vil ikke prøve å fjerne et tegn når de trykker på noe annet).

På toppen av alt, må vi tillate brukeren å trykke Komme tilbake for å fullføre navnetinngangen.

 hvis (playerNameTemp.Length> 0 && initialpress) if (Input.GetKeyDown (KeyCode.Return)) foreach (GUIText-tekst i FindObjectsOfType (typeof (GUIText)) som GUIText []) text.guiText.enabled = false;  GetComponent() .SetName (guiText.text); GetComponent() .enabled = true; aktivert = false; 

Så lenge brukeren har gjort noen form for inngang og lengde er større enn 0, navnet vil bli akseptert Alle våre tekstobjekter blir slettet, vi deaktiverer denne klassen, og vi slår på vår tredje klasse, Høy poengsum. Alle tre klassene må settes på vårt objekt i redaktøren.

Vi ringte nettopp Høy poengsum's SetName () funksjon, og tidligere vi ringte SetScore (). Hver av disse funksjonene setter bare verdiene av private variabler som vi sender inn nå til vårt rangliste.


Få tilgang til ditt leaderboard i enhet

På toppen av Høy poengsum Vi ønsker å erklære noen variabler. Først:

 offentlig GameObject BaseGUIText;

Dette er GUIText prefab at vi baserer vårt toppliste på. Du bør sørge for at teksten er forankret til midten til venstre, og venstrejustert. Du kan også velge en skrift her også.

 privat streng privateKey = "NØKKEN DU GENERERTE FØR"; privat streng AddScoreURL = "http://yoursite.com/AddScore.php?"; privat streng TopScoresURL = "http://yoursite.com/TopScores.php"; privat streng RankURL = "http://yoursite.com/GrabRank.php?"; privat int highscore; privat streng brukernavn; privat int rangering;

Vi trenger alle våre verdier fra før - nøkkelen du genererte, nettadressene du lastet opp dine PHP-filer til, og så videre. De høy poengsum og brukernavn variabler er satt ved hjelp av to offentlige funksjoner kalt SetScore () og SetName (), som vi brukte i forrige seksjon.

Tips: Det er veldig viktig at du legger spørsmålstegn etter AddScore.php og GrabRank.php! Disse lar deg passere variabler til PHP-filene dine.

Vi må bruke IEnumerators her for å håndtere våre SQL-spørringer, fordi vi må vente på svar. Vi starter vår første coroutine, AddScore (), så snart klassen er aktivert.

 IEnumerator AddScore (strengnavn, int score) string hash = Md5Sum (navn + score + privateKey); WWW ScorePost = nytt WWW (AddScoreURL + "name =" + WWW.EscapeURL (navn) + "& score =" + score + "& hash =" + hash); yield return ScorePost; hvis (ScorePost.error == null) StartCoroutine (GrabRank (navn));  ellers Feil (); 

Først lager vi vår hash med den private nøkkelen, ved hjelp av en funksjon for å lage en MD5 hash på samme måte som PHP MD5 (). Det er et eksempel på dette på Unitys community wiki.

Her, hvis serveren er utilgjengelig av en eller annen grunn, kjører vi en Feil() funksjon. Du kan velge hva du vil oppstå i feilsøkeren din. Hvis poengsummen blir riktig lagt opp, lanserer vi vår neste koroutin: GrabRank ().

 IEnumerator GrabRank (strengnavn) WWW RankGrabAttempt = ny WWW (RankURL + "name =" + WWW.EscapeURL (navn)); avkastning RankGrabAttempt; hvis (RankGrabAttempt.error == null) rank = System.Int32.Parse (RankGrabAttempt.text); StartCoroutine (GetTopScores ());  ellers Feil (); 

Igjen, vi får tilgang til nettstedet, og denne gangen lagrer vi rangen hvis den er vellykket tatt.

Nå kan vi bruke vår siste coroutine. Denne vil knytte alt sammen. Vi starter med å få tilgang til nettadressen for en sluttid:

 IEnumerator GetTopScores () WWW GetScoresAttempt = nytt WWW (TopScoresURL); yield return GetScoresAttempt; hvis (GetScoresAttempt.error! = null) Feil ();  annet 

Men denne gangen vil vi dele opp dataene vi mottar i en rekke strenger. Først av alt kan vi bruke en strengspalt som:

 streng [] textlist = GetScoresAttempt.text.Split (ny streng [] "\ n", "\ t", System.StringSplitOptions.RemoveEmptyEntries);

Dette sørger for at hvert resultat blir et nytt element i strengen vår, så lenge en ny linje eller en fane er funnet (som den vil være!). Vi kommer nå til å splitte dette inn i to ytterligere arrays som heter navnene og Poeng.

 streng [] Navn = ny streng [Mathf.FloorToInt (textlist.Length / 2)]; streng [] Scores = ny streng [Names.Length]; for (int i = 0; i < textlist.Length; i++)  if (i % 2 == 0)  Names[Mathf.FloorToInt(i / 2)] = textlist[i];  else Scores[Mathf.FloorToInt(i / 2)] = textlist[i]; 

Vi har nå laget to nye arrays som hver halvdel av størrelsen på den første gruppen. Vi deler deretter hver første streng i vår navnene array og hver andre i vår Poeng matrise.

Nå vil vi vise disse som tekst, så vi må posisjonere våre tre kolonner. Vi skal skalere dem på skjermen, slik at de passer til enhver oppløsning. Først vil vi erklære de opprinnelige posisjonene der vår titteltekst vil gå:

 Vector2 LeftTextPosition = ny Vector2 (0.22f, 0.85f); Vector2 RightTextPosition = ny Vector2 (0.76f, 0.85f); Vector2 CentreTextPosition = ny Vector2 (0.33f, 0.85f);

Og nå er vi klare til å lage våre tekstobjekter, basert på vår BaseGUIText ferdighus. Vi ordner titlene individuelt og setter inn tekst - for eksempel:

 GameObject Scoresheader = Instantiate (BaseGUIText, ny Vector2 (0.5f, 0.94f), Quaternion.identity) som GameObject; Scoresheader.guiText.text = "High Scores"; Scoresheader.guiText.anchor = TextAnchor.MiddleCenter; Scoresheader.guiText.fontSize = 35;

Når vi har gjort dette for alle våre titler, justerer vi våre stillinger slik at den nye teksten vises lavere ned.

 LeftTextPosition - = ny Vector2 (0, 0,062f); RightTextPosition - = ny Vector2 (0, 0,062f); CentreTextPosition - = ny Vector2 (0, 0,062f);

Neste kjører vi a til sløyfe som vil deterere gjennom hele vår topp 10 liste, sette navn, rang og score (og sørg for at teksten er forankret fornuftig), og deretter justere stillingene enda en gang. Hver iterasjon, vi kontrollerer om brukerens rang er lik rangeringen som vises, og i så fall rekryterer vi teksten slik at brukerens poengsum er uthevet i gult:

 for (int i = 0; i 

Og så til slutt sjekker vi om brukerens rang er over 10. Hvis det er, legger vi inn poengsummen deres nederst i ellevte spor, sammen med deres rang, og farger den gult.

 hvis (rang> 10) GameObject Score = Instantiate (BaseGUIText, RightTextPosition, Quaternion.identity) som GameObject; Score.guiText.text = "" + highscore; Score.guiText.anchor = TextAnchor.MiddleCenter; GameObject Name = Instantiate (BaseGUIText, CentreTextPosition, Quaternion.identity) som GameObject; Name.guiText.text = brukernavn; GameObject Rank = Instantiate (BaseGUIText, LeftTextPosition, Quaternion.identity) som GameObject; Rank.guiText.text = "" + (rang); Rank.guiText.anchor = TextAnchor.MiddleCenter; Score.guiText.material.color = Color.yellow; Name.guiText.material.color = Color.yellow; Rank.guiText.material.color = Color.yellow; 

Konklusjon

voilà; Vårt leaderboard er fullført! I kildefilene har jeg også inkludert en PHP-fil som vil ta tak i rekkene over og under brukerens brukernavn, slik at du kan vise dem nøyaktig hvor de er på tavlen.