Search…

Lưu Trữ Thông Tin Game với PlayerPrefs

25/09/20206 min read
Hướng dẫn sử dụng và thao tác với class PlayerPrefs trong Unity để lưu thông tin và trạng thái trong game.

Lưu điểm số, thông tin của game hay trạng thái lần chơi gần nhất là một điều quan trọng trong bất kì game nào. Người chơi không muốn mỗi lần chơi game ra phải chơi lại từ đầu giống như lúc vừa cài đặt, chơi lại từng màn đã qua.

Unity hỗ trợ sẵn tính năng lưu lại thông tin và trạng thái trong game thông qua lớp PlayerPrefs. Bài viết này sẽ hướng dẫn cách thao tác với PlayerPrefs để thực hiện tính năng "save game" với Unity.

Lưu trạng thái game

PlayerPrefs hỗ trợ lưu trữ 3 kiểu dữ liệu cơ bản: Int, Float, và String.

Tương ứng với 3 phương thức là SetInt, SetFloat, SetString.

Cả 3 phương thức này đều nhận vào:

  • 2 tham số trong đó tham số đầu tiên là tên đại điện cho giá trị được lưu trữ.
  • Tham số còn lại là nội dung cần lưu trữ.

Cần ghi nhớ key này để có thể lấy lại được nội dung đã lưu trữ về sau.

Mỗi khi thực hiện các hàm set thì dữ liệu chỉ thực sự được lưu xuống đĩa khi gọi hàm Save().

Dưới đây là cách dùng PlayerPrefs để lưu thông tin người chơi.

PlayerPrefs.SetString("username", "PhamNgocPhuoc");
PlayerPrefs.SetString("password", "stdio1235");
PlayerPrefs.SetInt("level", 10);

Truy xuất thông tin

Sau khi đã sử dụng PlayerPrefs để lưu trữ thông tin, sử dụng các phương thức GetInt, GetFloat, GetString để truy xuất các nội dụng đã lưu trữ.

Cũng giống như các phương thức Set ở trên, khi dùng các phương thức Get phải lựa chọn hàm tương ứng với kiểu dữ liệu đã ghi trước đó.

Các hàm Get(X) nhận vào 1 tham số là tên của key và có kiểu trả về X (với XInt, Float hoặc String).

Truy xuất các thông số đã lưu trữ ở trên như sau:

String _userName = PlayerPrefs.GetString("username");
String _password = PlayerPrefs.GetString ("password");
Int _level = PlayerPrefs.GetInt("level");

Nếu truyền vào 1  Key chưa tồn lại, ứng mỗi phương thức Get sẽ trả về cho 1 giá trị mặc định.

  • GetString thì giá trị đó là 1 chuỗi trỗng "".
  • GetFloat0.0f.
  • GetInt0.

Một số phương thức hỗ trợ

DeleteAll Xóa tất cả các key và giá trị đã được lưu trữ.
DeleteKey Xóa 1 key cụ thể.
GetFloat Trả về giá trị float tương ứng với key nếu key tồn tại.
GetInt Trả về giá trị int tương ứng với key nếu key tồn tại.
GetString Trả về giá trị string tương ứng với key nếu key tồn tại.
HasKey Trả về true nếu key tồn tại.
Save Lưu trữ tất cả dữ liệu được chỉnh sửa xuống đĩa.
SetFloat  Lưu giá trị float theo key vào bộ nhớ chính.
SetInt Lưu giá trị int theo key vào bộ nhớ chính.
SetString Lưu giá trị string theo key vào bộ nhớ chính.

Đường dẫn tệp tin lưu trữ

Nơi lưu trữ dữ liệu của PlayerPrefs ở các nền tảng khác nhau:

  • Mac OS: thư mục ~/Library/Preferences trong tập tin unity.[company name].[product name].plist.
  • Windows: lưu trữ ở registry dưới đường dẫn HKCU\Software\[company name]\[product name].
  • Linux: lưu trữ ở đường dẫn ~/.config/unity3d/[CompanyName]/[product name].
  • Windows Store Apps: %userprofile%\AppData\Local\Packages\[ProductPackageId]>\LocalState\playerprefs.dat.
  • WebPlayer: PlayerPrefs được lưu trữ trong tệp tin nhị phân với đường dẫn:
    • Mac OS X: ~/Library/Preferences/Unity/WebPlayerPrefs/
    • Windows: %APPDATA%\Unity\WebPlayerPrefs/

Bảo mật

Nếu truy cập vào trong đường dẫn lưu trữ PlayerPrefs thì có thể nhận thấy rằng các thông tin lưu trữ trong đó đều ở dạng nguyên gốc như lúc truyền vào. Điều này làm cho các thông tin này dễ dàng bị sửa đổi bất hợp pháp dẫn đến sai lệch thông tin của game hay đánh cắp thông tin người dùng (username, password, email, ...).

Dưới đây là màn chơi của game AngryBird được viết bằng Unity trong đó giá trị High Score được dùng để đánh dấu lại số điểm cao nhất từng đạt được của màn chơi đó sử dụng PlayerPrefs và thông tin mà PlayerPrefs lưu trữ. Giá trị này có thể dễ dàng được thay đổi bằng tay và dẫn đến game cũng bị ảnh hưởng theo.

Giá trị High Score trước khi bị chỉnh sửa:

lưu trữ thông tin game với playerprefs

Nơi lưu trữ giá trị High Score trên môi trường Windows:

Lưu trữ thông tin game với playerprefs

Giá trị High Score sau khi đã chỉnh sửa:

Lưu trữ thông tin game với playerprefs

Để hạn chế điều này có thể sử dụng các phương thức Encrypt hoặc Decrypt có sẵn trong C# để mã hóa dữ liệu trước khi lưu trữ xuống.

Bài viết hiện thực đơn giản 1 lớp dùng cho việc mã hoá và giải mã tập tin như sau:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace EncryptStringSample
{
    public static class StringCipher
    {
        // This constant string is used as a "salt" value for the PasswordDeriveBytes function calls.
        // This size of the IV (in bytes) must = (keysize / 8).  Default keysize is 256, so the IV must be
        // 32 bytes long.  Using a 16 character string here gives us 32 bytes when converted to a byte array.
        private static readonly byte[] initVectorBytes = Encoding.ASCII.GetBytes("tu89geji340t89u2");

        // This constant is used to determine the keysize of the encryption algorithm.
        private const int keysize = 256;

        public static string Encrypt(string plainText, string passPhrase)
        {
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
            {
                byte[] keyBytes = password.GetBytes(keysize / 8);
                using (RijndaelManaged symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.Mode = CipherMode.CBC;
                    using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes))
                    {
                        using (MemoryStream memoryStream = new MemoryStream())
                        {
                            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                byte[] cipherTextBytes = memoryStream.ToArray();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
            using(PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
            {
                byte[] keyBytes = password.GetBytes(keysize / 8);
                using(RijndaelManaged symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.Mode = CipherMode.CBC;
                    using(ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes))
                    {
                        using(MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using(CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                byte[] plainTextBytes = new byte[cipherTextBytes.Length];
                                int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }
    }
}

Cách sử dụng và thao tác với lớp StringCipher:

using System;
using System.Linq;

namespace EncryptStringSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter a password to use:");
            string password = Console.ReadLine();
            Console.WriteLine("Please enter a string to encrypt:");
            string plaintext = Console.ReadLine();
            Console.WriteLine("");

            Console.WriteLine("Your encrypted string is:");
            string encryptedstring = StringCipher.Encrypt(plaintext, password);
            Console.WriteLine(encryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Your decrypted string is:");
            string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
            Console.WriteLine(decryptedstring);
            Console.WriteLine("");

            Console.ReadLine();
        }
    }
}

Áp dụng mã hóa đối với trường hợp phía trên, lúc này giá trị HighScore sẽ được mã hóa thành một chuỗi kí tự không quy tắc.

Lưu trữ thông tin game với playerprefs

Chuỗi ký tự này có thể đảm bảo thông tin không bị đánh cắp hoặc chỉnh sửa trái phép nhưng chỉ ở mức tương đối an toàn. Các hacker vẫn có thể phát hiện ra thuật toán mã hóa đang dùng, mã hóa 1 chuỗi kết quả khác tương tự và thay thế vào đó. Nhưng dù sao thì đây vẫn là 1 cách tương đối tốt để tự bảo mật dữ liệu cho game.

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024