Tutorial de clasificación en línea de Unity

En este tutorial, mostraré cómo implementar una tabla de clasificación en línea en tu juego en Unity.

Esta es una continuación de un tutorial anterior: Unity Sistema de inicio de sesión con PHP y MySQL.

Tener una tabla de clasificación es una excelente manera de aumentar la rejugabilidad al agregar un nivel de competitividad a tu juego.

Al igual que antes, este tutorial requiere un servidor con cPanel junto con PHP y MySQLi (una versión mejorada de MySQL).

Siéntase libre de consultar el alojamiento premium asequible VPS o una alternativa más económica de Alojamiento compartido.

¡Así que continuemos!

Haciendo una modificación al script existente

Si siguió el tutorial anterior, ahora tendría un script llamado 'SC_LoginSystem'. Implementaremos la función de la tabla de clasificación al agregarle un código.

  • Abra el script 'SC_LoginSystem'

Primero, comenzamos agregando las variables necesarias:

    //Leaderboard
    Vector2 leaderboardScroll = Vector2.zero;
    bool showLeaderboard = false;
    int currentScore = 0; //It's recommended to obfuscate this value to protect against hacking (search 'obfuscation' on sharpcoderblog.com to learn how to do it)
    int previousScore = 0;
    float submitTimer; //Delay score submission for optimization purposes
    bool submittingScore = false;
    int highestScore = 0;
    int playerRank = -1;
    [System.Serializable]
    public class LeaderboardUser
    {
        public string username;
        public int score;
    }
    LeaderboardUser[] leaderboardUsers;

NOTA: La variable currentScore es lo que utilizará en el juego para realizar un seguimiento de las puntuaciones de los jugadores. Este valor se enviará al servidor y se almacenará en la base de datos, se recomienda ofuscar ese valor en orden para protegerse contra la piratería.

A continuación, agregamos 2 Enumeradores que serán responsables de enviar puntajes y recuperar la tabla de clasificación. Agregue el siguiente código al final del script antes del cierre del último paréntesis:

    //Leaderboard
    IEnumerator SubmitScore(int score_value)
    {
        submittingScore = true;

        print("Submitting Score...");

        WWWForm form = new WWWForm();
        form.AddField("email", userEmail);
        form.AddField("username", userName);
        form.AddField("score", score_value);

        using (UnityWebRequest www = UnityWebRequest.Post(rootURL + "score_submit.php", form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError)
            {
                print(www.error);
            }
            else
            {
                string responseText = www.downloadHandler.text;

                if (responseText.StartsWith("Success"))
                {
                    print("New Score Submitted!");
                }
                else
                {
                    print(responseText);
                }
            }
        }

        submittingScore = false;
    }

    IEnumerator GetLeaderboard()
    {
        isWorking = true;

        WWWForm form = new WWWForm();
        form.AddField("email", userEmail);
        form.AddField("username", userName);

        using (UnityWebRequest www = UnityWebRequest.Post(rootURL + "leaderboard.php", form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError)
            {
                print(www.error);
            }
            else
            {
                string responseText = www.downloadHandler.text;

                if (responseText.StartsWith("User"))
                {
                    string[] dataChunks = responseText.Split('|');
                    //Retrieve our player score and rank
                    if (dataChunks[0].Contains(","))
                    {
                        string[] tmp = dataChunks[0].Split(',');
                        highestScore = int.Parse(tmp[1]);
                        playerRank = int.Parse(tmp[2]);
                    }
                    else
                    {
                        highestScore = 0;
                        playerRank = -1;
                    }

                    //Retrieve player leaderboard
                    leaderboardUsers = new LeaderboardUser[dataChunks.Length - 1];
                    for(int i = 1; i < dataChunks.Length; i++)
                    {
                        string[] tmp = dataChunks[i].Split(',');
                        LeaderboardUser user = new LeaderboardUser();
                        user.username = tmp[0];
                        user.score = int.Parse(tmp[1]);
                        leaderboardUsers[i - 1] = user;
                    }
                }
                else
                {
                    print(responseText);
                }
            }
        }

        isWorking = false;
    }

La siguiente es la interfaz de usuario de la tabla de clasificación. Agrega el siguiente código después de void OnGUI():

    //Leaderboard
    void LeaderboardWindow(int index)
    {
        if (isWorking)
        {
            GUILayout.Label("Loading...");
        }
        else
        {
            GUILayout.BeginHorizontal();
            GUI.color = Color.green;
            GUILayout.Label("Your Rank: " + (playerRank > 0 ? playerRank.ToString() : "Not ranked yet"));
            GUILayout.Label("Highest Score: " + highestScore.ToString());
            GUI.color = Color.white;
            GUILayout.EndHorizontal();

            leaderboardScroll = GUILayout.BeginScrollView(leaderboardScroll, false, true);

            for (int i = 0; i < leaderboardUsers.Length; i++)
            {
                GUILayout.BeginHorizontal("box");
                if(leaderboardUsers[i].username == userName)
                {
                    GUI.color = Color.green;
                }
                GUILayout.Label((i + 1).ToString(), GUILayout.Width(30));
                GUILayout.Label(leaderboardUsers[i].username, GUILayout.Width(230));
                GUILayout.Label(leaderboardUsers[i].score.ToString());
                GUI.color = Color.white;
                GUILayout.EndHorizontal();
            }

            GUILayout.EndScrollView();
        }
    }

Agrega el siguiente código dentro del vacío OnGUI() (antes del paréntesis de cierre):

        //Leaderboard
        if (showLeaderboard)
        {
            GUI.Window(1, new Rect(Screen.width / 2 - 300, Screen.height / 2 - 225, 600, 450), LeaderboardWindow, "Leaderboard");
        }
        if (!isLoggedIn)
        {
            showLeaderboard = false;
            currentScore = 0;
        }
        else
        {
            GUI.Box(new Rect(Screen.width / 2 - 65, 5, 120, 25), currentScore.ToString());

            if (GUI.Button(new Rect(5, 60, 100, 25), "Leaderboard"))
            {
                showLeaderboard = !showLeaderboard;
                if (!isWorking)
                {
                    StartCoroutine(GetLeaderboard());
                }
            }
        }

Y, por último, anule Update(), que contendrá un código responsable de enviar la puntuación del jugador, una vez que cambie. Agregue el siguiente código al principio del script después de todas las variables:

    //Leaderboard
    void Update()
    {
        if (isLoggedIn)
        {
            //Submit score if it was changed
            if (currentScore != previousScore && !submittingScore)
            {
                if(submitTimer > 0)
                {
                    submitTimer -= Time.deltaTime;
                }
                else
                {
                    previousScore = currentScore;
                    StartCoroutine(SubmitScore(currentScore));
                }
            }
            else
            {
                submitTimer = 3; //Wait 3 seconds when it's time to submit again
            }

            //**Testing** Increase score on key press
            if (Input.GetKeyDown(KeyCode.Q))
            {
                currentScore += 5;
            }
        }
    }

Tenga en cuenta la parte **Probando**, dado que no tenemos un juego jugable, simplemente incrementamos el puntaje presionando Q (puede eliminarlo más tarde si ya tiene un juego con un sistema de puntuación, ej. recoger una moneda +1 punto, etc.)

Cuando presiona Reproducir e iniciar sesión, debe notar 2 elementos nuevos: el botón 'Leaderboard' y el valor de Puntuación en la parte superior de la pantalla.

Ahora pasamos a crear una tabla MySQL.

Creando una tabla MySQL

Las puntuaciones de los usuarios se almacenarán en una tabla MySQL separada.

  • Iniciar sesión en CPanel
  • Haga clic en "phpMyAdmin" en la sección BASES DE DATOS

  • Haga clic en la base de datos que creó en el tutorial anterior y luego haga clic en la pestaña SQL

  • Pegue el código a continuación en el editor de consultas y luego haga clic en "Go"
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";

--
-- Table structure for table `sc_user_scores`
--

CREATE TABLE `sc_user_scores` (
  `row_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `user_score` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for table `sc_user_scores`
--
ALTER TABLE `sc_user_scores`
  ADD PRIMARY KEY (`row_id`),
  ADD UNIQUE KEY `user_id` (`user_id`);

--
-- AUTO_INCREMENT for table `sc_user_scores`
--
ALTER TABLE `sc_user_scores`
  MODIFY `row_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
COMMIT;

La consulta anterior creará una nueva tabla llamada 'sc_user_scores' que almacenará las puntuaciones más altas junto con user_id como referencia a la tabla principal 'sc_users'.

La parte final es implementar la lógica del lado del servidor.

Implementación de la lógica del lado del servidor

La lógica del lado del servidor consistirá en scripts PHP que serán responsables de recibir/almacenar los puntajes y recuperar la tabla de clasificación.

El primer script es score_submit.php.

  • Cree un nuevo script PHP y pegue el siguiente código dentro de él:

score_submit.php

<?php
	if(isset($_POST["email"]) && isset($_POST["username"]) && isset($_POST["score"])){
		$errors = array();
		
		$email = $_POST["email"];
		$username = $_POST["username"];
		$submitted_score = intval($_POST["score"]);
		$user_id = -1;
		$current_highscore = -1;

		//Connect to database
		require dirname(__FILE__) . '/database.php';
		
		//Check if the user already registered, retrieve its user_id and score value (if exist)
		if ($stmt = $mysqli_conection->prepare("SELECT u.user_id, 
			(SELECT user_score FROM sc_user_scores WHERE user_id = u.user_id LIMIT 1) as user_score 
			FROM sc_users u WHERE u.email = ? AND u.username = ? LIMIT 1")) {
			
			/* bind parameters for markers */
			$stmt->bind_param('ss', $email, $username);
				
			/* execute query */
			if($stmt->execute()){
				
				/* store result */
				$stmt->store_result();

				if($stmt->num_rows > 0){
				
					/* bind result variables */
					$stmt->bind_result($user_id_tmp, $score_tmp);

					/* fetch value */
					$stmt->fetch();
					
					$user_id = $user_id_tmp;
					$current_highscore = $score_tmp;

				}else{
					$errors[] = "User not found.";
				}
				
				/* close statement */
				$stmt->close();
				
			}else{
				$errors[] = "Something went wrong, please try again.";
			}
		}else{
			$errors[] = "Something went wrong, please try again.";
		}
		
		//Submit new score
		if(count($errors) == 0){
			if(is_null($current_highscore) || $submitted_score > $current_highscore){
				
				if(is_null($current_highscore)){
					//Insert new record
					if ($stmt = $mysqli_conection->prepare("INSERT INTO sc_user_scores (user_id, user_score) VALUES(?, ?)")) {
						
						/* bind parameters for markers */
						$stmt->bind_param('ii', $user_id, $submitted_score);
							
						/* execute query */
						if($stmt->execute()){
							
							/* close statement */
							$stmt->close();
							
						}else{
							$errors[] = "Something went wrong, please try again.";
						}
					}else{
						$errors[] = "Something went wrong, please try again.";
					}
				}else{
					//Update existing record
					if ($stmt = $mysqli_conection->prepare("UPDATE sc_user_scores SET user_score = ? WHERE user_id = ? LIMIT 1")) {
						
						/* bind parameters for markers */
						$stmt->bind_param('ii', $submitted_score, $user_id);
							
						/* execute query */
						if($stmt->execute()){
							
							/* close statement */
							$stmt->close();
							
						}else{
							$errors[] = "Something went wrong, please try again.";
						}
					}else{
						$errors[] = "Something went wrong, please try again.";
					}
				}
				
			}else{
				$errors[] = "Submitted score is lower than the current highscore, skipping...";
			}
		}
		
		if(count($errors) > 0){
			echo $errors[0];
		}else{
			echo "Success";
		}
	}else{
		echo "Missing data";
	}
?>

El último script es leaderboard.php.

  • Cree un nuevo script PHP y pegue el siguiente código dentro de él:

clasificación.php

<?php
	//Retrieve our score along with leaderboard
	if(isset($_POST["email"]) && isset($_POST["username"])){
		$returnData = array();
		
		$email = $_POST["email"];
		$username = $_POST["username"];

		//Connect to database
		require dirname(__FILE__) . '/database.php';
		
		//Get our score and rank
		$returnData[] = "User";
		if ($stmt = $mysqli_conection->prepare("SELECT us.user_score,
			(SELECT COUNT(row_id) FROM sc_user_scores WHERE user_score >= us.user_score LIMIT 1) as rank
			FROM sc_user_scores us
			WHERE us.user_id = (SELECT user_id FROM sc_users WHERE email = ? AND username = ? LIMIT 1) LIMIT 1")) {
			
			/* bind parameters for markers */
			$stmt->bind_param('ss', $email, $username);
				
			/* execute query */
			if($stmt->execute()){
				
				/* store result */
				$stmt->store_result();

				if($stmt->num_rows > 0){
				
					/* bind result variables */
					$stmt->bind_result($score_tmp, $user_rank);

					/* fetch value */
					$stmt->fetch();
					
					//Append 
					$returnData[0] .= "," . $score_tmp . "," . $user_rank;

				}
				
				/* close statement */
				$stmt->close();
				
			}
		}
		
		//Get top 100 players
		if ($stmt = $mysqli_conection->prepare("SELECT u.username, us.user_score 
			FROM sc_users u RIGHT JOIN sc_user_scores us ON u.user_id = us.user_id 
			WHERE u.user_id IS NOT NULL ORDER BY us.user_score DESC LIMIT 100")) {
				
			/* execute query */
			if($stmt->execute()){
				
				$result = $stmt->get_result();

				while ($row = $result->fetch_assoc())
				{
					$returnData[] = $row["username"] . "," . $row["user_score"];
				}
				
				/* close statement */
				$stmt->close();
				
			}
		}
		
		//The returned string will use '|' symbol for separation between player data and ',' for separation inside the player data
		echo implode('|', $returnData);
	}else{
		echo "Missing data";
	}
?>
  • Cargue tanto score_submit.php como leaderboard.php en la misma carpeta en la que cargó los scripts PHP del tutorial anterior.

Una vez que todo esté configurado, cuando haga clic en la Tabla de clasificación, debería cargar su puntaje/clasificación junto con los 100 mejores jugadores en función de sus puntajes:

Artículos sugeridos
Sistema de inicio de sesión de Unity con PHP y MySQL
Creación de juegos multijugador en red en Unity
Crea un juego de coches multijugador con PUN 2
Unity agrega chat multijugador a PUN 2 Rooms
Sincronizar cuerpos rígidos a través de la red usando PUN 2
Crea un juego multijugador en Unity usando PUN 2
Guía para principiantes de Photon Network (clásico)