Hướng dẫn về bảng xếp hạng trực tuyến Unity

Trong hướng dẫn này, tôi sẽ trình bày cách triển khai bảng xếp hạng trực tuyến trong trò chơi của bạn trong Unity.

Đây là phần tiếp theo của hướng dẫn trước: Unity Hệ thống đăng nhập bằng PHP và MySQL.

Có bảng xếp hạng là một cách tuyệt vời để tăng khả năng chơi lại bằng cách tăng thêm mức độ cạnh tranh cho trò chơi của bạn.

Tương tự như trước, hướng dẫn này yêu cầu máy chủ có cPanel cùng với PHP và MySQLi (phiên bản cải tiến của MySQL).

Vui lòng kiểm tra dịch vụ lưu trữ VPS cao cấp có giá cả phải chăng hoặc giải pháp thay thế Shared Hosting rẻ hơn.

Vậy chúng ta hãy tiếp tục!

Thực hiện sửa đổi tập lệnh hiện có

Nếu bạn làm theo hướng dẫn ở trên, bây giờ bạn sẽ có một tập lệnh có tên 'SC_LoginSystem'. Chúng tôi sẽ triển khai tính năng bảng xếp hạng bằng cách thêm một số mã vào đó.

  • Mở tập lệnh 'SC_LoginSystem'

Đầu tiên, chúng ta bắt đầu bằng cách thêm các biến cần thiết:

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

LƯU Ý: Biến currentScore là biến bạn sẽ sử dụng trong trò chơi để theo dõi điểm số của người chơi. Giá trị này sẽ được gửi tới server và được lưu trữ trong cơ sở dữ liệu, bạn nên obfuscate giá trị đó để bảo vệ khỏi bị hack.

Tiếp theo, chúng tôi thêm 2 Điều tra viên sẽ chịu trách nhiệm gửi điểm và lấy bảng xếp hạng. Thêm mã bên dưới vào cuối tập lệnh trước khi đóng dấu ngoặc cuối cùng:

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

Tiếp theo là giao diện người dùng bảng xếp hạng. Thêm mã bên dưới sau khoảng trống 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();
        }
    }

Thêm mã bên dưới vào trong void OnGUI() (trước dấu ngoặc đóng):

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

Và cuối cùng, void Update(), sẽ chứa mã chịu trách nhiệm gửi điểm của người chơi khi nó thay đổi. Thêm mã bên dưới vào đầu tập lệnh sau tất cả các biến:

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

Lưu ý phần **Testing**, vì chúng tôi không có trò chơi nào có thể chơi được nên chúng tôi chỉ cần tăng điểm bằng cách nhấn Q (Bạn có thể xóa nó sau nếu bạn đã có trò chơi có hệ thống tính điểm, ví dụ:. thu thập xu +1 điểm, v.v.)

Khi nhấn Play và đăng nhập, bạn sẽ thấy 2 thành phần mới: nút 'Leaderboard' và giá trị Điểm ở đầu màn hình.

Bây giờ chúng ta chuyển sang tạo bảng MySQL.

Tạo bảng MySQL

Điểm của người dùng sẽ được lưu trữ trong một bảng MySQL riêng.

  • Đăng nhập vào CPanel
  • Nhấp vào "phpMyAdmin" trong phần CƠ SỞ DỮ LIỆU

  • Bấm vào cơ sở dữ liệu bạn đã tạo trong hướng dẫn trước, sau đó bấm vào tab SQL

  • Dán mã bên dưới vào trình soạn thảo truy vấn, sau đó nhấp vào "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;

Truy vấn ở trên sẽ tạo một bảng mới có tên 'sc_user_scores' sẽ lưu trữ điểm cao nhất cùng với user_id làm tham chiếu đến bảng 'sc_users' chính.

Phần cuối cùng là triển khai logic phía máy chủ.

Triển khai logic phía máy chủ

Logic phía máy chủ sẽ bao gồm các tập lệnh PHP chịu trách nhiệm nhận/lưu trữ điểm số và truy xuất bảng xếp hạng.

Tập lệnh đầu tiên là score_submit.php.

  • Tạo một tập lệnh PHP mới và dán đoạn mã bên dưới vào bên trong nó:

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

Tập lệnh cuối cùng là leaderboard.php.

  • Tạo một tập lệnh PHP mới và dán đoạn mã bên dưới vào bên trong nó:

bảng xếp hạng.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";
	}
?>
  • Tải cả Score_submit.php và Leaderboard.php lên cùng thư mục mà bạn đã tải lên tập lệnh PHP từ hướng dẫn trước.

Sau khi mọi thứ đã được thiết lập, khi bạn nhấp vào Bảng xếp hạng, nó sẽ tải điểm/thứ hạng của bạn cùng với 100 người chơi hàng đầu dựa trên điểm số của họ:

Bài viết được đề xuất
Hệ thống đăng nhập Unity với PHP và MySQL
Hướng dẫn cho người mới bắt đầu về Mạng Photon (Cổ điển)
Giới thiệu về Photon Fusion 2 trong Unity
Tạo trò chơi nhiều người chơi trong Unity bằng PUN 2
Xây dựng các trò chơi nối mạng nhiều người chơi trong Unity
Nén dữ liệu nhiều người chơi và thao tác bit
Tạo trò chơi ô tô nhiều người chơi với PUN 2