Unity数据持久化完全指南:从PlayerPrefs到SQLite

在游戏开发中,数据持久化是一个至关重要的话题。无论是简单的游戏设置还是复杂的游戏存档,都需要将数据保存到永久存储设备中,以确保玩家在下次启动游戏时仍能继续游戏过程、保留设置等。Unity为开发者提供了多种实现数据持久化的方式,本指南将详细介绍这些方法并给出示例代码,帮助开发者根据项目需求选择合适的方案。

本项目所有代码均已上传至Github。源码链接


1.PlayerPrefs: 轻量级键值存储

PlayerPrefs是Unity内置的简单键值对存储系统,适合保存少量数据,如玩家偏好设置、游戏解锁信息等。使用它只需调用几个API即可完成读写操作。

PlayerPrefs 是 Unity 提供的一种简单的方式,用于在游戏运行时保存和加载数据。它使用键值对的形式存储数据,并将数据存储在用户计算机上的注册表或文件中,具体取决于平台。

PlayerPrefs 适用于存储相对较小的数据,如玩家设置、高分记录等。它不适合存储大量数据或复杂的数据结构。对于大型数据,建议使用其他持久性解决方案,如文件系统或数据库。

保存和加载玩家分数、用户名和音量设置示例

实现功能

  • 输入玩家昵称、当前分数、滑动条控制当前音量。
  • 点击Save按钮,保存当前分数和当前音量。
  • 点击Load按钮,加载上次存储的分数和音量设置。
  • 点击Reset按钮,重置当前界面的用户信息和设置。

数据信息变更之后点击Save按钮,之后再次改变设置,点击Load会出现Save的设置,点击Reset按钮,所有设置清零。

实现代码

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour
{
    //用作 PlayerPrefs 中存储数据的键名
    private const string ScoreKey = "PlayerScore";
    private const string VolumeKey = "GameVolume";
    private const string HighScoreKey = "HighScore";
    private const string PlayerNameKey = "PlayerName";

    //存储玩家的分数、音量、最高分数和名称,以及用于临时存储加载的数据
    private int playerScore;
    private float gameVolume;
    private int highScore;
    private string playerName = "Player";

    private int savedPlayerScore;
    private float savedGameVolume;
    private int savedHighScore;
    private string savedPlayerName;

    //用于显示分数、设置音量滑动条、显示音量数值和输入玩家名称的 UI 元素
    public Text scoreText;
    public Slider volumeSlider;
    public Text volumeValueText;
    public InputField playerNameInput;

    void Start()
    {
        //加载之前保存的数据并更新 UI
        LoadData();
        UpdateUI();
    }

    void Update()
    {
        //检测按下空格键,如果按下,则增加分数并更新 UI
        if (Input.GetKeyDown(KeyCode.Space))
        {
            playerScore += 10;
            Debug.Log("Score: " + playerScore);
            UpdateUI();
        }
    }

    private void SaveData()
    {
        //将当前的分数、音量、最高分数和玩家名称保存到 PlayerPrefs 中
        PlayerPrefs.SetInt(ScoreKey, playerScore);
        PlayerPrefs.SetFloat(VolumeKey, gameVolume);
        PlayerPrefs.SetInt(HighScoreKey, highScore);

        if (!string.IsNullOrEmpty(playerName))
        {
            PlayerPrefs.SetString(PlayerNameKey, playerName);
        }
        else
        {
            PlayerPrefs.DeleteKey(PlayerNameKey);
        }

        PlayerPrefs.Save();
    }

    private void LoadData()
    {
        //用于从 PlayerPrefs 中加载之前保存的数据。首先将加载的数据存储在临时变量中,然后将这些值赋给实际的变量。
        //这样可以确保加载的数据不会被覆盖
        savedPlayerScore = PlayerPrefs.GetInt(ScoreKey, 0);
        savedGameVolume = PlayerPrefs.GetFloat(VolumeKey, 0.5f);
        savedHighScore = PlayerPrefs.GetInt(HighScoreKey, 0);

        if (PlayerPrefs.HasKey(PlayerNameKey))
        {
            savedPlayerName = PlayerPrefs.GetString(PlayerNameKey);
        }
        else
        {
            savedPlayerName = "Player";
        }

        playerScore = savedPlayerScore;
        gameVolume = savedGameVolume;
        highScore = savedHighScore;
        playerName = savedPlayerName;
    }

    private void UpdateUI()
    {
        //更新显示分数、音量滑动条、音量数值和玩家名称的 UI 元素
        scoreText.text = "Score: " + playerScore;
        volumeSlider.value = gameVolume;
        volumeValueText.text = gameVolume.ToString("0.00");
        playerNameInput.text = playerName;
    }

    private void ApplyVolume()
    {
        //应用当前的音量设置,在这里只是打印日志
        Debug.Log("Game volume set to: " + gameVolume);
    }

    public void SetVolume(float volume)
    {
        //音量滑动条的事件回调函数,用于更新音量值并应用音量设置
        gameVolume = volume;
        ApplyVolume();
        UpdateUI();
    }

    public void SaveSettings()
    {
        //保存当前的游戏设置
        SaveData();
    }

    public void LoadSettings()
    {
        //加载之前保存的数据,然后更新 UI 并应用音量设置
        LoadData();
        UpdateUI();
        ApplyVolume();
    }

    public void DeleteData()
    {
        //删除所有保存的数据,并重置为默认值。它使用 PlayerPrefs.DeleteAll() 删除所有保存的键值对。
        PlayerPrefs.DeleteAll();
        playerScore = 0;
        gameVolume = 0.5f;
        highScore = 0;
        playerName = "Player";
        UpdateUI();
        ApplyVolume();
    }

    public void SetPlayerName(string name)
    {
        //玩家名称输入框的事件回调函数,用于更新玩家名称并保存数据。
        playerName = name;
        SaveData();
        UpdateUI();
    }
}

PlayerPrefs用法

使用 PlayerPrefs 的主要方法包括:

  • PlayerPrefs.SetInt(key, value): 将一个整数值与指定的键关联。
  • PlayerPrefs.GetInt(key, defaultValue): 获取与指定键关联的整数值,如果键不存在,则返回默认值。
  • PlayerPrefs.SetFloat(key, value): 将一个浮点数值与指定的键关联。
  • PlayerPrefs.GetFloat(key, defaultValue): 获取与指定键关联的浮点数值,如果键不存在,则返回默认值。
  • PlayerPrefs.SetString(key, value): 将一个字符串值与指定的键关联。
  • PlayerPrefs.GetString(key, defaultValue): 获取与指定键关联的字符串值,如果键不存在,则返回默认值。
  • PlayerPrefs.HasKey(key): 检查指定的键是否存在。
  • PlayerPrefs.DeleteKey(key): 删除与指定键关联的值。
  • PlayerPrefs.DeleteAll(): 删除所有保存的键值对。
  • PlayerPrefs.Save(): 将所有修改过的数据从内存写入磁盘。

在使用 PlayerPrefs 时,建议遵循以下规则:

  1. 使用有意义的键名,以便于识别和管理。
  2. 为每种类型的数据使用不同的键名前缀,以避免冲突。
  3. 定期调用 PlayerPrefs.Save() 方法,以确保数据被及时写入磁盘。
  4. 在加载数据时,始终提供合理的默认值,以防键不存在。
  5. 在不再需要保存的数据时,记得删除相应的键值对。
  6. 考虑在初始化时删除所有旧数据,以确保数据的一致性。

优点

简单易用、轻量级、跨平台兼容性好。

缺点

只能存储简单数据类型,数据安全性较差(可被玩家修改),频繁读写会影响性能。适用于保存少量非敏感游戏数据。


2.文件存储: 灵活保存数据

使用C#的文件IO操作,可以将任意数据类型保存到设备的文件系统中。这种方式灵活性很高,但需要开发者自行处理数据的序列化和反序列化。将数据保存到设备本地文件系统中的方式,通常会将数据序列化为某种格式(如JSON、XML或二进制格式)后写入文件。

保存和加载玩家数据示例

实现功能

  • 保存和加载一个PlayerData类的实例,包括玩家名称、分数、等级、解锁道具和游戏进度等数据。
  • 使用BinaryFormatter进行二进制序列化,提高序列化和反序列化的性能。
  • 自动加载之前保存的数据,并在游戏退出时自动保存当前数据。
  • 通过UI按钮和输入框,允许用户查看和修改玩家数据。
  • 提供”Save”、”Load”、”Delete”和”Open Folder”按钮,分别用于保存、加载、清空数据和打开持久化数据文件所在的文件夹。
  • 实现了基本的版本控制,可以检测数据版本是否与当前版本匹配。

数据信息变更之后点击Save按钮,之后再次改变设置,点击Load会出现Save的设置。点击Open按钮,打开当前文件的存储地址。点击Delete按钮,所有设置清零。

实现代码

using UnityEngine;
using UnityEngine.UI;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine.EventSystems;

[System.Serializable]
public class PlayerData
{
    public string playerName;
    public int playerScore;
    public int playerLevel;
    public bool[] unlockedItems;
    public int gameProgress;
}

public class FileIO : MonoBehaviour
{
    public InputField nameInput;
    public Text scoreText;
    public Text levelText;
    public Button saveButton;
    public Button loadButton;
    public Button openFolderButton;
    public Button deleteButton;

    private string filePath;
    private PlayerData playerData = new PlayerData();

    void Start()
    {
        filePath = Application.persistentDataPath + "/player.dat";
        LoadPlayerData();
        UpdateUI();

        saveButton.onClick.AddListener(SavePlayerData);
        loadButton.onClick.AddListener(LoadPlayerData);
        openFolderButton.onClick.AddListener(OpenPersistentDataFolder);
        deleteButton.onClick.AddListener(DeletePlayerData);
    }

    void Update()
    {
        // 按空格键增加分数
        if (Input.GetKeyDown(KeyCode.Space))
        {
            playerData.playerScore += 10;
            UpdateLevel();
            UpdateUI();
        }
    }

    private void OnApplicationQuit()
    {
        SavePlayerData();
    }

    private void SavePlayerData()
    {
        try
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream stream = new FileStream(filePath, FileMode.Create);

            // 添加版本控制
            formatter.Serialize(stream, Application.version);
            formatter.Serialize(stream, playerData);

            stream.Close();
            Debug.Log("Player data saved successfully!");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"Failed to save player data: {e.Message}");
        }
    }

    private void LoadPlayerData()
    {
        if (File.Exists(filePath))
        {
            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                FileStream stream = new FileStream(filePath, FileMode.Open);

                // 检查版本
                string version = formatter.Deserialize(stream) as string;
                if (version != Application.version)
                {
                    Debug.LogWarning("Player data version mismatch. Data may be incompatible.");
                }

                playerData = (PlayerData)formatter.Deserialize(stream);
                stream.Close();
                UpdateUI();
                Debug.Log("Player data loaded successfully!");
            }
            catch (System.Exception e)
            {
                Debug.LogError($"Failed to load player data: {e.Message}");
            }
        }
    }

    private void UpdateUI()
    {
        nameInput.text = playerData.playerName;
        scoreText.text = $"Score: {playerData.playerScore}";
        levelText.text = $"Level: {playerData.playerLevel}";
    }

    private void UpdateLevel()
    {
        // 简单的等级更新逻辑,每积累100分升一级
        playerData.playerLevel = playerData.playerScore / 100 + 1;
    }

    private void OpenPersistentDataFolder()
    {
        string folderPath = Application.persistentDataPath;
        System.Diagnostics.Process.Start(folderPath);
    }

    private void DeletePlayerData()
    {
        playerData.playerName = "";
        playerData.playerScore = 0;
        playerData.playerLevel = 1;
        playerData.unlockedItems = new bool[5];
        playerData.gameProgress = 0;
        SavePlayerData();
        UpdateUI();
        Debug.Log("Player data deleted successfully!");
    }
}

这个示例使用JsonUtility将PlayerData对象序列化为JSON字符串存储在文件中。文件存储的优点是灵活性好、适用于中等数据量、跨平台性好。缺点是安全性较差、频繁IO操作会影响性能、占用存储空间。适用于存储中等复杂度的游戏数据。

文件存储用法

  1. 确定要保存的数据类型,并使用[System.Serializable]属性标记该类型。
  2. 获取持久化数据的文件路径,通常使用Application.persistentDataPath获取一个可写入的路径。
  3. 使用BinaryFormatterJsonUtility等工具将数据序列化为字符串或二进制格式。
  4. 通过File.WriteAllTextFileStream将序列化后的数据写入文件。
  5. 加载数据时,从文件中读取数据,然后使用相应的工具反序列化为原始数据类型。

优点

  1. 简单易用: 文件操作相对简单直观,只需要几行代码就可以实现数据的保存和加载。
  2. 灵活性强: 可以保存任意数据类型,包括自定义的复杂数据结构。
  3. 跨平台性好: 文件存储可以在大多数平台(Windows、macOS、iOS、Android等)上使用。
  4. 适合中等数据量: 对于中等数据量(几百KB到几MB),文件存储性能良好。

缺点

  1. 安全性较差: 保存在本地文件系统中的数据相对不太安全,容易被窃取或篡改。
  2. 频繁IO操作影响性能: 如果需要频繁读写文件,可能会影响游戏性能。
  3. 占用存储空间: 根据数据量的大小,文件可能会占用一定的存储空间。
  4. 版本控制困难: 如果数据结构发生变化,可能需要做额外的版本控制和兼容性处理。

3.JSON序列化: 交换结构化数据

JSON是一种轻量级的数据交换格式,具有可读性好、跨平台性强等优点。Unity提供了JsonUtility类简化了对象和JSON的相互转换。

将游戏数据存储在本地文件系统中,通常使用JSON二进制格式。在Unity中,可以使用Application.persistentDataPath获取应用程序的持久化数据路径,然后读写文件。

保存和加载游戏设置示例

实现功能

实现了完整的游戏设置功能,包括保存、加载和应用设置。它使用了JSON序列化来存储设置数据,并通过Unity的UI组件让玩家方便地修改设置。当设置发生变化时,会自动保存到磁盘文件,下次启动游戏时会自动读取并应用这些设置。

分别改动游戏场景中的各个数据信息,变更之后点击Save按钮。重新运行游戏场景之后会出现Save的设置。

实现代码

using UnityEngine;
using UnityEngine.UI;
using System.IO;

[System.Serializable]
public class GameSettings
{
    public float musicVolume = 1f;
    public int screenResolutionIndex = 0;
    public bool fullScreen = true;
    public int qualityLevel = 2;
}

public class JSONDemo : MonoBehaviour
{
    public Slider musicVolumeSlider;
    public Dropdown resolutionDropdown;
    public Toggle fullScreenToggle;
    public Dropdown qualityDropdown;

    private string filePath;
    private GameSettings settings = new GameSettings();

    void Start()
    {
        filePath = Application.persistentDataPath + "/settings.json";
        LoadSettings();
        ApplySettings();
    }

    public void SaveSettings()
    {
        settings.musicVolume = musicVolumeSlider.value;
        settings.screenResolutionIndex = resolutionDropdown.value;
        settings.fullScreen = fullScreenToggle.isOn;
        settings.qualityLevel = qualityDropdown.value;

        string jsonData = JsonUtility.ToJson(settings);
        File.WriteAllText(filePath, jsonData);
    }

    public void LoadSettings()
    {
        if (File.Exists(filePath))
        {
            string jsonData = File.ReadAllText(filePath);
            settings = JsonUtility.FromJson<GameSettings>(jsonData);
        }
    }

    public void ApplySettings()
    {
        musicVolumeSlider.value = settings.musicVolume;
        AudioListener.volume = settings.musicVolume;

        resolutionDropdown.value = settings.screenResolutionIndex;
        Resolution[] resolutions = Screen.resolutions;
        resolutionDropdown.options.Clear();
        foreach (Resolution resolution in resolutions)
        {
            resolutionDropdown.options.Add(new Dropdown.OptionData($"{resolution.width} x {resolution.height}"));
        }
        Screen.SetResolution(resolutions[settings.screenResolutionIndex].width, resolutions[settings.screenResolutionIndex].height, settings.fullScreen);

        fullScreenToggle.isOn = settings.fullScreen;

        qualityDropdown.value = settings.qualityLevel;
        QualitySettings.SetQualityLevel(settings.qualityLevel);
    }
}

JSON序列化用法

在Unity中实现数据持久化,JSON序列化是一种常见且有效的方法。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于ECMAScript的一个子集,采用完全独立于语言的文本格式来存储和表示数据。

在Unity中,可以使用JSON来序列化(将对象转换为JSON字符串)和反序列化(将JSON字符串转换回对象)数据,从而实现数据的持久化。

优点

  1. 简单易用,对开发者友好。
  2. 可以存储复杂的数据结构,如自定义类对象。
  3. 数据格式灵活,可以选择JSON、XML或二进制等。
  4. 数据安全性较好,数据存储在本地文件系统中。

缺点

  1. 性能略低于内存存储或数据库存储。
  2. 需要处理文件读写异常。
  3. 跨平台兼容性可能存在问题,需要处理不同操作系统的文件路径差异。

JSON序列化的优点是简单易用、可读性好、序列化后文件较小。缺点是性能开销比二进制格式高、不支持所有数据类型。适用于保存结构化的中小型数据集。


4.XML序列化: 适用于复杂数据结构

XML格式天生支持表示层次化的数据结构,可以很好地处理自定义类型、集合等复杂对象。在Unity中,我们可以使用System.Xml命名空间提供的类进行XML序列化和反序列化操作。XML 序列化是一种常用的数据持久化方法之一,它可以将对象的状态转换为 XML 格式,然后保存到文件中。Unity 中使用 XML 序列化通常需要结合 .NET 中提供的 System.Xml.Serialization 命名空间。

保存和加载游戏进度示例

实现功能

保存进度和打开进度文件的功能。当玩家修改游戏进度时,如增加生命值或收集金币,进度会自动保存到磁盘文件中。玩家可以使用 Open 按钮打开保存的进度文件进行查看。

实现代码

using UnityEngine;
using System.IO;
using System.Xml.Serialization;
using UnityEngine.UI;
using System.Diagnostics;

[System.Serializable]
public class GameProgress
{
    public int currentLevel = 1;
    public float playerHealth = 100f;
    public int[] coinsCollected = new int[10];
    public WeaponData equippedWeapon;
}

[System.Serializable]
public class WeaponData
{
    public string name = "Pistol";
    public int damage = 10;
    public int ammoAmount = 100;
}

public class XMLDemo : MonoBehaviour
{
    public Text currentLevelText;
    public Slider playerHealthSlider;
    public Text[] coinTexts;
    public InputField weaponNameInputField;
    public InputField weaponDamageInputField;
    public InputField weaponAmmoInputField;
    public Button saveProgressButton;
    public Button openFileButton;

    private string filePath;
    private GameProgress progress;

    void Start()
    {
        filePath = Application.persistentDataPath + "/progress.xml";
        LoadProgress();
        UpdateUI();

        saveProgressButton.onClick.AddListener(SaveProgress);
        openFileButton.onClick.AddListener(OpenFile);

        weaponNameInputField.onEndEdit.AddListener(UpdateWeaponName);
        weaponDamageInputField.onEndEdit.AddListener(UpdateWeaponDamage);
        weaponAmmoInputField.onEndEdit.AddListener(UpdateWeaponAmmo);
    }

    public void SaveProgress()
    {
        XmlSerializer serializer = new XmlSerializer(typeof(GameProgress));
        using (FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(stream, progress);
        }
    }

    private void LoadProgress()
    {
        if (File.Exists(filePath))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(GameProgress));
            using (FileStream stream = new FileStream(filePath, FileMode.Open))
            {
                progress = (GameProgress)serializer.Deserialize(stream);
            }
            UpdateUI();
        }
        else
        {
            ResetGameProgress();
        }
    }

    private void ResetGameProgress()
    {
        progress = new GameProgress();
        UpdateUI();
    }

    public void IncreasePlayerHealth(float amount)
    {
        progress.playerHealth = Mathf.Clamp(progress.playerHealth + amount, 0f, 100f);
        playerHealthSlider.value = progress.playerHealth;
        SaveProgress();
    }

    public void CollectCoin(int index)
    {
        progress.coinsCollected[index]++;
        SaveProgress();
        UpdateCoinsUI();
    }

    private void UpdateUI()
    {
        UpdateCurrentLevelUI();
        UpdatePlayerHealthUI();
        UpdateCoinsUI();
        UpdateWeaponUI();
    }

    private void UpdateCurrentLevelUI()
    {
        currentLevelText.text = $"Current Level: {progress.currentLevel}";
    }

    private void UpdatePlayerHealthUI()
    {
        playerHealthSlider.value = progress.playerHealth;
    }

    private void UpdateCoinsUI()
    {
        for (int i = 0; i < coinTexts.Length; i++)
        {
            coinTexts[i].text = progress.coinsCollected[i].ToString();
        }
    }

    private void UpdateWeaponUI()
    {
        weaponNameInputField.text = progress.equippedWeapon.name;
        weaponDamageInputField.text = progress.equippedWeapon.damage.ToString();
        weaponAmmoInputField.text = progress.equippedWeapon.ammoAmount.ToString();
    }

    private void UpdateWeaponName(string name)
    {
        progress.equippedWeapon.name = name;
        SaveProgress();
    }

    private void UpdateWeaponDamage(string damageString)
    {
        if (int.TryParse(damageString, out int damage))
        {
            progress.equippedWeapon.damage = damage;
            SaveProgress();
        }
    }

    private void UpdateWeaponAmmo(string ammoString)
    {
        if (int.TryParse(ammoString, out int amount))
        {
            progress.equippedWeapon.ammoAmount = amount;
            SaveProgress();
        }
    }

    private void OpenFile()
    {
        Process.Start("explorer.exe", filePath.Replace("/", "\\"));
    }
}

XML序列化用法

Unity 中使用 XML 序列化进行数据持久化的一般步骤:

  1. 定义可序列化的数据类:首先,你需要定义一个包含要保存的数据的类,并且标记为可序列化。这通常使用 C# 中的 [System.Serializable] 特性来实现。
  2. 使用 XmlSerializer 将对象序列化为 XML:在需要保存数据的时候,使用 XmlSerializer 类来将数据对象序列化为 XML 格式。
  3. 将序列化后的 XML 数据写入文件:将序列化后的 XML 数据写入到文件中,以便在应用程序关闭后能够重新加载。
  4. 从文件中读取 XML 数据并反序列化:在应用程序启动时,读取保存的 XML 数据文件,并使用 XmlSerializer 将其反序列化为对象。

优点

  1. 支持复杂数据结构: XML格式天生支持表示层次化的数据结构,可以很好地处理自定义类型、集合等复杂对象。
  2. 可读性好: XML数据具有很好的可读性,便于调试和编辑。
  3. 跨平台性强: XML是一种广为接受的标准格式,可实现良好的跨平台兼容性。

缺点

  1. 占用存储空间较大: 相比二进制格式,XML格式通常会占用更多存储空间。
  2. 序列化性能较低: XML序列化和反序列化的性能通常低于二进制格式。
  3. 可读性损失: 相比JSON等格式,XML在某些情况下的可读性会略差。

总的来说,XML序列化非常适合表示和存储具有复杂层次结构的数据,如游戏进度、配置信息等。如果项目对性能和存储空间要求不是非常苛刻,XML可以作为一种不错的数据持久化选择。

5.SQLite: 强大的关系型数据库

对于需要存储大量数据、进行复杂查询的项目,使用关系型数据库如SQLite会是不错的选择。SQLite是一个开源的嵌入式关系数据库,无需单独的服务器进程,可直接集成在应用中使用。

存储和查询游戏物品数据示例

实现功能

image-20240417171734876

Insert按钮插入一些物品数据,点击Query按钮查看所有插入的物品数据,点击Clear按钮会清除Items表中的所有数据。点击Clear按钮会清除Items表中的所有数据,再次点击Query按钮,文本框显示Data cleared successfully!

image-20240417173139936

实现代码

using UnityEngine;
using Mono.Data.Sqlite;
using System.Data;
using System.IO;
using UnityEngine.UI;

public class ItemDatabase : MonoBehaviour
{
    private string connectionString;
    private string filePath;

    public InputField itemNameInput;
    public InputField itemDescriptionInput;
    public InputField itemPriceInput;
    public Button insertButton;
    public Button queryButton;
    public Button clearButton;
    public Text itemListText;

    void Start()
    {
        filePath = Application.persistentDataPath + "/items.sqlite";
        connectionString = "URI=file:" + filePath;

        CreateTables();

        insertButton.onClick.AddListener(InsertItem);
        queryButton.onClick.AddListener(QueryAllItems);
        clearButton.onClick.AddListener(ClearData);
    }

    private void CreateTables()
    {
        using (IDbConnection dbConnection = new SqliteConnection(connectionString))
        {
            dbConnection.Open();

            IDbCommand dbCommand = dbConnection.CreateCommand();
            string createTableQuery = "CREATE TABLE IF NOT EXISTS Items (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, Description TEXT, Price INTEGER)";
            dbCommand.CommandText = createTableQuery;
            dbCommand.ExecuteNonQuery();
        }
    }

    private void InsertItem()
    {
        string name = itemNameInput.text;
        string description = itemDescriptionInput.text;
        int price;
        if (int.TryParse(itemPriceInput.text, out price))
        {
            using (IDbConnection dbConnection = new SqliteConnection(connectionString))
            {
                dbConnection.Open();

                IDbCommand dbCommand = dbConnection.CreateCommand();
                string insertQuery = "INSERT INTO Items (Name, Description, Price) VALUES (@Name, @Description, @Price)";
                dbCommand.CommandText = insertQuery;
                dbCommand.Parameters.Add(new SqliteParameter("@Name", name));
                dbCommand.Parameters.Add(new SqliteParameter("@Description", description));
                dbCommand.Parameters.Add(new SqliteParameter("@Price", price));
                dbCommand.ExecuteNonQuery();

                Debug.Log("Item inserted successfully!");
            }
        }
        else
        {
            Debug.LogError("Invalid price input!");
        }
    }

    private void QueryAllItems()
    {
        itemListText.text = "";
        using (IDbConnection dbConnection = new SqliteConnection(connectionString))
        {
            dbConnection.Open();

            IDbCommand dbCommand = dbConnection.CreateCommand();
            string queryAllQuery = "SELECT * FROM Items";
            dbCommand.CommandText = queryAllQuery;

            IDataReader reader = dbCommand.ExecuteReader();
            while (reader.Read())
            {
                int id = reader.GetInt32(0);
                string name = reader.GetString(1);
                string description = reader.GetString(2);
                int price = reader.GetInt32(3);

                itemListText.text += $"Id: {id}, Name: {name}, Description: {description}, Price: {price}\n";
            }
            reader.Close();
        }
    }

    private void ClearData()
    {
        using (IDbConnection dbConnection = new SqliteConnection(connectionString))
        {
            dbConnection.Open();

            IDbCommand dbCommand = dbConnection.CreateCommand();
            string clearQuery = "DELETE FROM Items";
            dbCommand.CommandText = clearQuery;
            dbCommand.ExecuteNonQuery();

            itemListText.text = "Data cleared successfully!";
        }
    }
}

SQLite用法

创建数据库和表

首先需要创建SQLite数据库文件,通常将其存储在应用程序的持久化数据路径中(如Unity中的Application.persistentDataPath)。然后使用SQL语句创建所需的表,定义表的结构和字段。例如:

csharpCopy codeusing (IDbConnection dbConnection = new SqliteConnection(connectionString))
{
    dbConnection.Open();
    IDbCommand dbCommand = dbConnection.CreateCommand();
    string createTableQuery = "CREATE TABLE IF NOT EXISTS Items (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, Description TEXT, Price INTEGER)";
    dbCommand.CommandText = createTableQuery;
    dbCommand.ExecuteNonQuery();
}

插入数据

使用SQL的INSERT语句将数据插入到表中。例如:

csharpCopy codestring insertQuery = "INSERT INTO Items (Name, Description, Price) VALUES (@Name, @Description, @Price)";
dbCommand.CommandText = insertQuery;
dbCommand.Parameters.Add(new SqliteParameter("@Name", name));
dbCommand.Parameters.Add(new SqliteParameter("@Description", description));
dbCommand.Parameters.Add(new SqliteParameter("@Price", price));
dbCommand.ExecuteNonQuery();

查询数据

使用SQL的SELECT语句从表中查询数据。例如:

csharpCopy codestring queryAllQuery = "SELECT * FROM Items";
dbCommand.CommandText = queryAllQuery;
IDataReader reader = dbCommand.ExecuteReader();
while (reader.Read())
{
    int id = reader.GetInt32(0);
    string name = reader.GetString(1);
    // ... 获取其他字段的值
}
reader.Close();

更新数据

使用SQL的UPDATE语句更新表中的数据。

删除数据

使用SQL的DELETE语句从表中删除数据。

事务支持

SQLite支持事务,可以保证数据操作的完整性和一致性。

数据迁移和版本控制

如果数据库结构发生变化,需要进行数据迁移和版本控制,确保新旧版本的数据兼容性。

优点

  • 支持SQL语句,可以进行复杂的数据查询和操作。
  • 适合存储大量结构化数据,如游戏物品、玩家信息、任务进度等。
  • 提供事务支持,确保数据的一致性和完整性。
  • 跨平台兼容性良好,可以在多个平台上使用相同的数据库文件。

缺点

  • 对于简单的数据存储可能过于臃肿
  • 需要引入第三方库,增加项目体积
  • 可能存在与Unity版本兼容性问题

这个示例展示了如何在Unity中集成SQLite数据库,创建表、插入数据和查询所有数据。这种基于关系型数据库的数据持久化方案适用于需要高效存储和查询大量结构化数据的复杂游戏项目。

总的来说,Unity为开发者提供了多种数据持久化方案,从简单的PlayerPrefs到强大的SQLite数据库,涵盖了不同场景的需求。在选择方案时,需要权衡项目复杂度、数据量大小、性能要求等多个因素。合理使用数据持久化,可以极大提升游戏的用户体验。

© 版权声明
THE END
喜欢就支持一下吧
点赞2 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    请登录后查看评论内容