Unity C # Сохраненная игра через двоичное форматирование без сохранения данных - PullRequest
0 голосов
/ 18 октября 2018

Обновление: эта проблема может быть вызвана проблемами бинарного форматирования при редактировании данных в существующих полях.Комментарий, который я больше не могу найти, описывает альтернативные методы, которые я сейчас реализую.Обновится после того, как эти методы будут предприняты, если это решение проблемы.

Я должен начать с того, что я студент, поэтому, пожалуйста, будьте осторожны со мной, я получил уведомление, когда впервые начал приезжать сюда, и надеюсь, чтоПродолжай учиться.Я новичок в единстве, мой C # приличный, но полный пробелов, так как мое обучение было довольно ужасным.Я смотрел сотни часов уроков по единству и изучаю новые концепции, которые я изучаю по 4 часа каждую ночь после того, как я ухожу с работы, поэтому, если вы видите что-то, просто дайте мне знать.

На самом деле это проблемаЯ имел некоторое время, но думал, что я исправил несколько месяцев назад.Я пытался сохранить игры в первый раз, и читал в двоичном формате и тому подобное, чтобы сохранить.У меня были проблемы с его установкой, но мне удалось его сохранить и правильно извлечь из файла.Я проверяю, что данные, поступающие в файл, верны, а выходящие данные верны, и даже сделали данные частными с помощью функции управления, так что ничто не сможет получить к ним доступ и изменить их, не выполняя мою отладку.И все же после того, как я покидаю область, в которой я определяю данные, они меняются, не имея доступа к моей функции обновления.

Чтобы разбить его, у меня есть класс PlayerType, в котором хранится вся информация об игроке, включая мой менеджер сцены, и он сериализует ее и сохраняет в виде списка.Я создаю цикл for, используя текущую длину загруженного списка, используя экземпляр класса saveload (это то, что содержит список сохраненных игр и доступ к файлу), и он перебирает экземпляры моих кнопок в порядке.Слот 1 будет нажимать, чтобы сохранить игру 1 и так сказать.Проблема, с которой я сталкиваюсь - щелчок по слоту 1, щелчок по слоту 16, так же как и по слоту 2. На первый взгляд кажется, что кнопки на какой слот идут практически случайно.Здесь я должен сказать, что не уверен, идет ли он в неправильный слот или просто переименовывает имена игроков неправильно, но в любом случае это не так.

Вот моя функция загрузки

public void Load() //Loads file from .gd file after checking if exists, then deserializes it back into a list
{
    accessDataPath(false);

    Debug.Log("Size of Save File" + listSize);

    for (int i = 0; i < listSize; i++)
    {
        Debug.Log("First Loop " + SavedGames[i].returnName());
    }

    foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
    {

        Debug.Log("Second Loop" + player.returnName());
    }
}

и вот моя функция accessDataPath

public void accessDataPath(bool isSave)
{
    //This stucture checks if file exists or not, then checks if saving or loading for 4 outcomes


    if (isSave && SavedGames == null)//if saving and save file to update has nothing in it
    {
        Debug.Log("Error attempted to save null list in SaveLoad.accessDataPath!");
    }


    if (File.Exists(Application.persistentDataPath + "/SavedGames.gd"))//if the file exists
    {
        if (isSave)//if you are saving
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
            bf.Serialize(file, SavedGames);
            file.Close();
        }

        else //if you are not saving
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
            SavedGames = (List<PlayerType>)bf.Deserialize(file);
            file.Close();
            listSize = SavedGames.Count;
        }
    }
    else//if the file does not exist
    {
        if (isSave)//if you are saving
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Create(Application.persistentDataPath + "/SavedGames.gd");
            bf.Serialize(file, SavedGames);
            file.Close();
        }
        else//if you are not saving
            Debug.Log("Error Loading File. Does not Exist!");//Display Later that file does not exist to user
    }
    updateNames();
}//End accessDataPath

вот функция, которая обновляет имена

public void updateNames()
{
    for (int i = 0; i < listSize; i++)
    {
        SavedGames[i].updateName("Player " + i);
        Debug.Log("Bump " + i);
    }
}

Как видите, я проверяю, существует ли она, затем проверьте, сохраняю ли я или загружаю.Вызов функции сохранения с истиной, а SavedGames - это список, извлекаемый из файла.Теперь, после запуска изменения имени, я могу проверить, работает ли оно, и оно работает.Запустив проверку здесь, они все возвращаются как правильное имя, однако к тому времени, когда он возвращается к моей функции загрузки и запускает мой первый цикл, они ошибаются, он никогда не покидает этот раздел кода, но как только он выходит из области, которую они изменяютбыть почти случайным.Теперь у меня была эта проблема некоторое время, но я подумал, что это связано с моим файлом, возможно, с поврежденными данными, поэтому я удалил игру сохранения и создал новые для тестирования.Числа изменились, но все еще были не правы.Это говорит мне о том, что файл каким-то образом влияет на имена, хотя я переименую их почти сразу после их выхода.Я не привык к загрузке или сохранению в файлах, поэтому я не уверен, с чего начать.

Я опубликую больше своего кода ниже на случай, если какой-то из них окажется явно неправильным, я также ценю советпо структуре, когда я брал уроки в колледже по программированию игр, но они были ужасны и заполнены пробелами, даже если вы не нашли ответа на мою проблему.

Сначала я включил или выключил настройку системы меню., вызванный событиями onclick.

public class SaveMenu : MonoBehaviour {
public GameObject menu;
bool isActive;
bool isSave;
PopulateSave playerSaves;


public static SaveMenu instance;


void Awake()
{
    isSave = false;
    instance = this;
    menu = GameObject.Find ("SaveList");
    menu.SetActive (false);
    isActive = false;
    playerSaves = menu.GetComponentInChildren<PopulateSave> ();
}

public bool getActive()
{
    return isActive;
}
public void toggleMenu(bool saveLoad)
{
    if (isActive) 
    {
        menu.SetActive (false);
        isActive = false;
    } else 
    {
        menu.SetActive (true);
        isActive = true;
    }
    bool isSave = saveLoad;
    menu.transform.Find("Scroll View").transform.Find("Viewport").transform.Find("Content").transform.Find("NewSave").gameObject.SetActive(isSave);
    Debug.Log(isSave);


}
public void updateSave()
{
    playerSaves.startList();//Seems redundant but is used to make this access public with limited use
}
public bool getSave()
{
    return isSave;
}
}

После того, как все создано, я называю populateaves ребенком, потому что у меня не было проблем с ним, когда я пытался вызвать его через инспектора.PopulateSaves - это место, где находится большая часть моего функционального кода.

public class PopulateSave : MonoBehaviour{
public Button NewSave;
public Button OldSaves; // This is our prefab object that will be exposed in the inspector



void Awake()
{
    GameManager.instance.saveStorage.Load();

    Populate();
}

void Update()
{

}

void Populate()
{
    Button newObj = Instantiate(NewSave, transform); // Create GameObject instance

    newObj.name = "NewSave";

    startList();

}
public void startList()
{
    clearList();

    for (int i = 1; i <  GameManager.instance.saveStorage.returnCount() + 1; i++)
    {
        createButton(i);
    }
    Debug.Log("Done creating");
}
public void updateList(PlayerType newSave)
{
    Button newObj;
    newObj = (Button)Instantiate(OldSaves, transform);
    //GameManager.instance.saveStorage.Save(i);
}
public void clearList()
{
    GameObject[] gameObjects;
    gameObjects = GameObject.FindGameObjectsWithTag("SaveSlot");
    for (var i = 0; i < gameObjects.Length; i++)
        Destroy(gameObjects[i]);
    Debug.Log("Done Destroying");
}
public void ButtonClicked(int slot)
{

    if (SaveMenu.instance.getSave())
    {
        Debug.Log("Save");
        GameManager.instance.saveStorage.Save(slot);
    }
    else
    {
        Debug.Log("load");
        GameManager.instance.currentPlayer.newPlayer(slot);
    }

}
public void createButton(int i)
{

    // Create new instances of our prefab until we've created as many as is in list
    Button newObj = (Button)Instantiate(OldSaves, transform);

    //increment slot names
    newObj.name = "Slot " + i;
    newObj.GetComponentInChildren<Text>().text = "Slot " + i;
    newObj.onClick.AddListener(() => ButtonClicked(i));
}
}

Теперь я также никогда не писал слушателю со сценарием, как у меня здесь, может ли этот раздел назначать им неправильный номер?Я проверил, чтобы убедиться, что все мои индексы имеют правильные числа, если они не равны 1.Один из моих циклов может использовать настройку, которая имеет значение 1, но я больше беспокоюсь о том, чтобы снять это с земли и исправить особенности в этой точке.

Вот хранилище моего игрока

[System.Serializable]
public class PlayerType {
string playerName;
SceneManagement currentScene;

public PlayerType()
    {
    playerName = "Starting Name";
    }
public void updateName(string name)
{
    //Debug.Log("New Name: " + name);
    playerName = name;
}
public void updateScene(SceneManagement newScene)
{
    currentScene = newScene;
}
public string returnName()
{
    return playerName;
}
public SceneManagement returnScene()
{
    return currentScene;
}

public void newPlayer(int slotSave)
{

    //Tmp player has wrong name from this point, initiated wrong?

    //Debug.Log("New Name to update" + tmpPlayer.returnName());
    this.updateName(GameManager.instance.saveStorage.returnSaves(slotSave).returnName());
    this.updateScene(GameManager.instance.saveStorage.returnSaves(slotSave).returnScene());
}
}

Обновление: Bump корректно отображает 0-16, затем Размер файла сохранения отображает 17 полных сохранений. Первый цикл начинается с вывода «First Loop Player 15» в общей сложности 16 раз. Затем он отображает 16, так что последний правильный, хотя и один.я думаюВторой цикл делает то же самое, что и первый, что неудивительно.

Я оставил вызов updateNames, но закомментировал строки и выполнил его, включая устранение неровностей.Он начинается с 17-ти сохранений снова и 16-кратной итерации 15-го игрока, однако на этот раз вокруг последнего отображается «Temp Name», которое я определяю только один раз в начале сценария sceneManagement для текущего игрока, оно никогда не должно быть сохраненои даже если бы это должно было быть перезаписано моим именем цикла, по крайней мере, это было моим намерением.

SceneManagement

[System.Serializable] 
public class SceneManagement : MonoBehaviour {

DialogueManager dialogueManager;
PlayerType currentPlayer;

bool isSave;
bool isActive;
string sceneName;
int lineCount;


void Start()
{
    isSave = false;
    //loads player object for the current save
    currentPlayer = new PlayerType();
    currentPlayer.updateName ("Temp Name");



    //This loads the prologue from the DB and sets the dialoguemanager up, defaults to prologue for now but can be updated to another scene later
    isActive = true;
    sceneName = "Prologue";
    string conn = "URI=file:" + Application.dataPath + "/Text/Game.sqlite3";

    List<string> sceneLines = new List<string>();
    List<string> sceneCharacters = new List<string>();
    int tmpInt;
    string tmpString = "NOTHING";
    int count = 0;
    lineCount = 1;

    IDbConnection dbConn;
    dbConn = (IDbConnection)new SqliteConnection (conn);
    dbConn.Open (); //Open database connection
    IDbCommand dbCmd = dbConn.CreateCommand();
    string sqlQuery = "SELECT Line, Flags, Character, Image, Text, Color FROM Prologue";
    dbCmd.CommandText = sqlQuery;
    IDataReader reader = dbCmd.ExecuteReader ();


    while (reader.Read ()) {
        tmpInt = reader.GetInt32 (0);
        tmpString = reader.GetString (1);
        sceneCharacters.Add(reader.GetString (2));
        tmpInt = reader.GetInt32 (3);
        sceneLines.Add(reader.GetString (4));
        tmpString = reader.GetString (5);

        //Debug.Log (tmpString);
        //Debug.Log (count);
        count++;
    }


    reader.Close ();
    reader = null;
    dbCmd.Dispose ();
    dbCmd = null;
    dbConn.Close ();
    dbConn = null;



    dialogueManager = new DialogueManager(sceneCharacters, sceneLines, lineCount);
}
//These are the returnvalues that might be used, may or may not be kept depending on future use.
public string getSceneName()
{
    return sceneName;
}
public int getLineCount()
{
    return lineCount;
}
public bool getSave()
{
    return isSave;
}
//These return the lines for displaymanager, preventing it from directly interacting with dialoguemanager
public string getNextLine()
{
    return dialogueManager.getNextLine();
}
   public string getNextCharacter()
{
    return dialogueManager.getNextCharacter();
}

//This function sets up visibility and is only accessed to properly display the screen after the scene has been started.
public void startScene ()
{
    GameManager.instance.screenDisplay.changeVisible(true);
}
void Update()
{
    if (Input.GetKeyDown ("space") & isActive) {
        dialogueManager.incrementCurrentLine ();
        GameManager.instance.screenDisplay.changeText(dialogueManager.getNextLine ());
        GameManager.instance.screenDisplay.changeCharacter(dialogueManager.getNextCharacter ());
    }




    if (Input.GetKeyDown ("escape")) {
        if (!PauseMenu.instance.getActive () && !SaveMenu.instance.getActive ()) 
        {
            PauseMenu.instance.toggleMenu ();
        } 
        else if (SaveMenu.instance.getActive()) 
        {
            SaveMenu.instance.toggleMenu (true);
            PauseMenu.instance.toggleMenu ();
        }
        else if (PauseMenu.instance.getActive())
        {
            PauseMenu.instance.toggleMenu ();
        }
    }

}
public void initialScreen()
{
    SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
void OnDisable()
{
    SceneManager.sceneLoaded -= OnLevelFinishedLoading;

}
void OnLevelFinishedLoading (Scene scene, LoadSceneMode mode)
{
    GameManager.instance.screenDisplay.initialScreen(dialogueManager.getNextLine(), dialogueManager.getNextCharacter(), null, null,
        null, null, null, null);
}
public PlayerType returnPlayer()
{
    return currentPlayer;
}
}

, хотя я здесь, это тоже мой GameManager, хотяЯ не испортил это тонну.В основном, используя его как DontDestroyOnLoad, чтобы удерживать все остальное на этом этапе.

public class GameManager : MonoBehaviour{

public static GameManager instance;
public DisplayScreen screenDisplay;
public SceneManagement managerScene;
public SaveLoad saveStorage;
public PlayerType currentPlayer;
//leave out until load data is setup
//PlayerType currentPlayer; 



void Awake()
{
    instance = this;
    DontDestroyOnLoad (transform.gameObject);
    managerScene = gameObject.AddComponent(typeof(SceneManagement)) as SceneManagement;
    screenDisplay = gameObject.AddComponent (typeof(DisplayScreen)) as DisplayScreen;
    saveStorage = gameObject.AddComponent (typeof(SaveLoad)) as SaveLoad;

    //initialize player and sceneManager here, but only load through main menu options
    //of new game or load game
}

void update()
{

}
}

Я продолжил устранять неполадки и довел это до особенно запутанной для меня части.Я вынул все журналы отладки и добавил 3 цикла в свою функцию загрузки, вот они.

Debug.Log("LOAD CALLED");

    accessDataPath(false);

    for (int i = 0; i < listSize; i++)
    {
        Debug.Log("First Loop " + SavedGames[i].returnName());
        SavedGames[i].updateName("Updated Player " + (i + 1));

    }

    for (int i = 0; i < listSize; i++)
    {
        Debug.Log("Second Loop " + SavedGames[i].returnName());

    }

    foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
    {

        Debug.Log("Third Loop " + player.returnName());
    }

Итак, первый отображает проигрывателя 15, затем правильно отображает первые циклы 1-15, а затем имя временного имени снова,до сих пор не выяснил, почему это появляется, но я думаю, что это связано.Тогда второй цикл повторяет все неправильно.Буквально измененные и спина к спине петли ошибочны, единственное отличие состоит в том, что они выходят за рамки цикла for.Я запустил третий цикл, используя foreach, чтобы увидеть, изменился ли тип вызова, и это не так.

Даже изменение имени, а затем немедленный вызов цикла для проверки показывает, что значения меняются.Я думаю, что это может быть связано с тем, как я храню объекты, но я не уверен, как может возникнуть проблема.Он не обнуляется, и имена меняются, чтобы быть одинаковыми каждый раз, поэтому он не является абсолютно случайным.Я публикую это здесь сразу после того, как нашел это, надеясь, что скоро я решу это, но также надеюсь, что если я не сделаю, кто-то еще может что-то заметить.У меня есть следующие 3 часа, чтобы работать над этим, поэтому я буду пытаться все это время проверять время от времени.Заранее спасибо за всех, кто может взглянуть на это для меня.

1 Ответ

0 голосов
/ 25 октября 2018

Хорошо, так что я наконец-то закончил реализацию.Мне пришлось поменять тонну моего кода, и в итоге я сделал комбинацию объект / список-обертку, используя JSON.По сути, проблема в том, что двоичный форматировщик портит объекты после десериализации их.Каждый раз, когда я обновлял значения своих объектов, они менялись случайным образом безо всякой причины без доступа.

Это было удивительно, так как примерно половина постов, которые я прочитал о сохранении, говорит, что это хорошо, другие как Этот один говорит, что это плохая практика.Сначала я провел некоторое исследование, но не столкнулся с негативными аспектами его использования.У меня все еще есть проблемы, но Json успешно сохраняет данные, и мои объекты не портятся.Я почти уверен, что это была проблема, поскольку единственные разделы, которые я изменил, были структурой значений моих объектов общедоступной для сериализации и реализовали структуру json в моем скрипте SaveLoad. Это видео было очень полезно для общей структуры и начала работы, а Эта ветка помогла мне с устранением неполадок, когда я столкнулся с несколькими проблемами.

Я должен также отметить, что одну вещь я не уловил некоторое время.В то время как Json может загружать списки, исходный загружаемый объект не должен быть списком.Я пытался сохранить список моего PlayerType непосредственно в папку, но это не сработает.Я закончил тем, что создал быстрый объект, который содержал мой список, и затем сохранил объект.Поскольку везде, где я читал, говорилось, что списки были хорошими, потребовалось время, чтобы обнаружить, что это является причиной моей проблемы.Он не выдавал мне никаких ошибок, просто возвращал пустую строку, о которой говорили большинство потоков, потому что она не была публичной или сериализуемой.

В любом случае, здесь есть надежда, что моя борьба и поиски ответов могут помочь, поскольку вещи, которые я обнаружил, были довольно разбросаны и с ними трудно столкнуться.

...