הדרכה מקוונת של Unity Leaderboard

במדריך זה, אני אראה כיצד ליישם טבלת הישגים מקוונת במשחק שלך בUnity.

זהו המשך של מדריך קודם: Unity מערכת כניסה עם PHP ו-MySQL.

להחזיק בלוח הישגים היא דרך מצוינת להגדיל את יכולת המשחק החוזרת על ידי הוספת רמת תחרותיות למשחק שלך.

כמו קודם, מדריך זה דורש שרת עם cPanel יחד עם PHP ו-MySQLi (גרסה משופרת של MySQL).

אל תהסס לבדוק את הפרימיום VPS אירוח או חלופה זולה יותר Shared Hosting.

אז בואו נמשיך!

ביצוע שינוי בסקריפט הקיים

אם עקבת אחר המדריך שלמעלה, כעת יהיה לך סקריפט בשם 'SC_LoginSystem'. אנו ניישם את תכונת Leaderboard על ידי הוספת קוד כלשהו אליו.

  • פתח את הסקריפט 'SC_LoginSystem'

ראשית, נתחיל בהוספת המשתנים הדרושים:

    //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;

הערה: המשתנה currentScore הוא המשתנה שבו תשתמש במשחק כדי לעקוב אחר תוצאות השחקנים. ערך זה יוגש ל-שרת ויישמר במסד הנתונים, מומלץ לערפל את הערך הזה על מנת להגן מפני פריצה.

לאחר מכן, נוסיף 2 סופרים שיהיו אחראים על הגשת הציונים ושליפת ה-Leaderboard. הוסף את הקוד למטה בסוף הסקריפט לפני סגירת הסוגר האחרון:

    //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;
    }

הבא הוא ממשק המשתמש של Leaderboard. הוסף את הקוד למטה אחרי ה-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();
        }
    }

הוסף את הקוד למטה בתוך ה-Void OnGUI() (לפני סוגר הסגירה):

        //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());
                }
            }
        }

ולבסוף, void Update(), שיכיל קוד שאחראי על הגשת הניקוד של השחקן, ברגע שהוא משתנה. הוסף את הקוד למטה בתחילת הסקריפט אחרי כל המשתנים:

    //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;
            }
        }
    }

שימו לב לחלק **Testing**, מכיוון שאין לנו משחק שניתן לשחק בו, אנחנו פשוט מגדילים את הניקוד על ידי לחיצה על Q (תוכל להסיר אותו מאוחר יותר אם כבר יש לך משחק עם שיטת ניקוד, למשל אספו מטבע + נקודה אחת וכו')

כאשר אתה לוחץ על Play והתחבר, אתה אמור לשים לב ל-2 אלמנטים חדשים: כפתור 'Leaderboard' וערך הניקוד בראש המסך.

כעת אנו עוברים ליצור טבלת MySQL.

יצירת טבלת MySQL

ציוני המשתמשים יאוחסנו בטבלת MySQL נפרדת.

  • היכנס ל- CPanel
  • לחץ על "phpMyAdmin" מתחת לסעיף מסדי נתונים

  • לחץ על מסד הנתונים שיצרת במדריך הקודם ולאחר מכן לחץ על לשונית SQL

  • הדבק את הקוד למטה בעורך השאילתות ולאחר מכן לחץ "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;

השאילתה למעלה תיצור טבלה חדשה בשם 'sc_user_scores' אשר תשמור את הציונים הגבוהים ביותר יחד עם user_id כהתייחסות לטבלת 'sc_users' הראשית.

החלק האחרון הוא הטמעת לוגיקה בצד השרת.

הטמעת לוגיקה בצד השרת

הלוגיקה בצד השרת תהיה מורכבת מסקריפטים של PHP שיהיו אחראים על קבלת/אחסון הציונים ושליפת ה-leaderboard.

הסקריפט הראשון הוא score_submit.php.

  • צור סקריפט PHP חדש והדבק בתוכו את הקוד למטה:

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";
	}
?>

הסקריפט האחרון הוא leaderboard.php.

  • צור סקריפט PHP חדש והדבק בתוכו את הקוד למטה:

leaderboard.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";
	}
?>
  • העלה גם score_submit.php וגם leaderboard.php לאותה תיקייה שהעלית סקריפטים של PHP מהמדריך הקודם.

לאחר שהכל הוגדר, כשאתה לוחץ על Leaderboard זה אמור לטעון את הניקוד/דירוג שלך יחד עם 100 השחקנים המובילים על סמך התוצאות שלהם: