更新:此问题可能是由于在现有字段中编辑数据的二进制格式程序问题。我找不到的评论描述了我当前正在实现的替代方法。如果可以解决问题,将在尝试这些方法后更新。
我首先要说我是一名学生,所以请放心,我第一次来这里时就收到了报道,希望继续学习。我是一个刚接触团结的人,我的C#不错,但是由于我的学校很糟糕,所以充满了空白。我已经看了数百小时的统一教程,并且正在每天下班后每晚4个小时学习新概念,所以,如果您看到什么,请告诉我。
这实际上是我一段时间以来遇到的问题,但以为我几个月前已解决。我是第一次尝试保存游戏,然后读入二进制格式并进行保存。我在安装它时遇到了问题,但是我设法将其保存并从文件中正确提取。我验证输入到文件中的数据是正确的,并且输出的数据是正确的,并且甚至通过控制功能将数据设为私有,因此在不跳过调试的情况下,任何人都无法访问和更改它。但是,在离开范围定义数据的范围之后,无需访问更新功能。
要对其进行分解,我有一个名为PlayerType的类,该类存储包括我的场景管理器在内的所有播放器信息,并将其序列化并将其保存为列表文件。我使用saveload类的实例(使用保存游戏列表和文件访问权限)使用加载列表的当前长度创建一个for循环,并按顺序实例化我的按钮。插槽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);
}
}
如您所见,我确认它是否存在,然后检查我是否正在保存或加载。保存函数使用true调用,而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;
}
}
在创建所有内容后,我将populatesaves称为孩子,因为当我尝试通过检查器将其放入时,我不存在它的问题。我的大多数功能代码都在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());
}
}
更新: 凹凸正确显示0-16 然后“保存文件的大小”显示总共17次保存 然后,首先循环输出“ First Loop Player 15”,总共进行16次 然后它显示16,所以最后一个是正确的,尽管我猜是一个。 毫不奇怪,第二个循环的作用与第一个相同。
我保留了对updateNames的调用,但是注释了所有行并运行了它,包括删除了凹凸。 它再次以17次保存开始,并且播放器15进行了16次迭代,但是这一次在最后一个播放器周围显示了“临时名称”,我仅在我的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个小时,我将继续工作,因此,我将不时地尝试整个过程。预先感谢任何可能帮我看看的人。
答案 0 :(得分:0)
好的,所以我终于完成了实现。我不得不交换大量的代码,最后使用JSON完成了包装对象/列表的组合。本质上,问题在于二进制格式化程序将对象反序列化后将其弄乱了。每次我更新对象值时,如果不访问它们,它们将无缘无故地随意改变。
这很令人惊讶,因为我阅读的关于保存的帖子中大约有一半说它是好的,而其他This之类的人则说这是不好的做法。我最初进行了一些研究,但没有发现使用它的负面影响。我仍然遇到问题,但是Json成功地保留了数据,并且我的对象没有弄乱。我很确定这是问题所在,因为我更改的唯一部分是将对象的值结构公开以进行序列化,并将json结构实现到了SaveLoad脚本中。 This视频对于整体结构和入门非常有用,This线程在遇到一些问题时帮助我进行了故障排除。
我还应该指出,有一阵子我没有抓到一件事。虽然Json可以加载列表,但是要加载的初始对象一定不能是列表。我试图将我的PlayerType列表直接保存到文件夹中,但不会这样做。我最终创建了一个包含我的列表的快速对象,然后保存了该对象。因为我到处都读到说列表很好,所以花了一段时间才发现这是造成我问题的一部分。它没有给我任何错误,只是返回了一个空字符串,大多数线程说这是因为它不是公共的或可序列化的。
无论如何,希望我的努力和寻找答案可能会有所帮助,因为我发现的事情非常分散且难以理解。