Unity 온라인 리더보드 튜토리얼
이 튜토리얼에서는 Unity에서 게임에 온라인 리더보드를 구현하는 방법을 보여 드리겠습니다.
이것은 이전 튜토리얼인 Unity PHP 및 MySQL을 사용한 로그인 시스템의 연속입니다.
순위표를 갖는 것은 게임에 경쟁력 수준을 추가하여 재생 가능성을 높이는 좋은 방법입니다.
이전과 마찬가지로 이 튜토리얼에는 PHP 및 MySQLi(MySQL의 향상된 버전)와 함께 cPanel이 있는 서버가 필요합니다.
부담 없이 저렴한 프리미엄 VPS 호스팅이나 더 저렴한 공유 호스팅 대안을 확인해 보세요.
그럼 계속하자!
기존 스크립트 수정
위의 튜토리얼을 따랐다면 이제 'SC_LoginSystem'라는 스크립트가 생겼을 것입니다. 우리는 리더보드 기능에 몇 가지 코드를 추가하여 구현할 것입니다.
- '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
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;
}
다음은 리더보드 UI입니다. 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;
}
}
}
**테스트** 부분에 주목하세요. 플레이 가능한 게임이 없으므로 Q를 눌러 점수를 올리기만 하면 됩니다(점수 시스템이 있는 게임이 이미 있는 경우 나중에 제거할 수 있습니다. 예:.코인 +1포인트 모으기 등)
재생을 누르고 로그인하면 2개의 새로운 요소, 즉 'Leaderboard' 버튼과 화면 상단의 점수 값을 볼 수 있습니다.
이제 MySQL 테이블을 생성해 보겠습니다.
MySQL 테이블 생성
사용자 점수는 별도의 MySQL 테이블에 저장됩니다.
- CPanel에 로그인
- DATABASES 섹션에서 "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_users' 테이블에 대한 참조로 user_id와 함께 가장 높은 점수를 저장하는 'sc_user_scores'이라는 새 테이블을 생성합니다.
마지막 부분은 서버 측 논리를 구현하는 것입니다.
서버측 로직 구현
서버측 로직은 점수 수신/저장 및 순위표 검색을 담당하는 PHP 스크립트로 구성됩니다.
첫 번째 스크립트는 score_submit.php입니다.
- 새 PHP 스크립트를 만들고 그 안에 아래 코드를 붙여넣습니다.
점수_제출.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 스크립트를 만들고 그 안에 아래 코드를 붙여넣습니다.
리더보드.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";
}
?>
- 이전 튜토리얼에서 PHP 스크립트를 업로드한 동일한 폴더에 Score_submit.php 및 Leaderboard.php를 모두 업로드합니다.
모든 것이 설정된 후 리더보드를 클릭하면 점수를 기준으로 상위 100명의 플레이어와 함께 점수/순위가 로드됩니다.