我有一款弹珠机风格的游戏,其中有各种具有独特特征的球。我希望允许玩家在进入游戏场景之前创建他们想要使用的球类型的装载,这将在单独的装载场景中发生。
我在两点上遇到了麻烦:
1. The reference is being returned as null and thus when I enter the game scene, it crashes with an error stating such
2. The SaveLoad system isn't recording the items in each loadout slot, though I suppose this is a different issue.
以下是此过程中涉及的每个脚本的代码:
游戏附加到每个场景中的空游戏对象
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Game : MonoBehaviour
{
[HideInInspector] public ulong highestScore = 0;
[HideInInspector] public ulong totalCurrentCredits = 0;
[HideInInspector] public string playerName = " ";
[HideInInspector] public short ballSlotsUnlocked = 5;
[HideInInspector] public string[] ballSetAssigned = new string[20];
[HideInInspector] public List<GameObject> slots;
private void Awake()
{
for (int i = 0; i < slots.Count; i++)
{
try
{
ballSetAssigned[i] = slots[i].GetComponent<EquipmentSlot>().GetBallName();
Debug.Log(ballSetAssigned[i] + " From gameObject " + gameObject.name + " inside " + SceneManager.GetActiveScene().name);
}
catch
{
Debug.Log($"null from Game Awake at index {i}");
continue;
}
}
}
public void SaveGame()
{
SaveLoad.SaveGame(this);
}
public void LoadGame()
{
GameData gameData = SaveLoad.LoadGame();
highestScore = gameData.highestScore;
totalCurrentCredits = gameData.totalCurrentCredits;
playerName = gameData.playerName;
ballSlotsUnlocked = gameData.ballSlotsUnlocked;
for (int i = 0; i < ballSetAssigned.Length; i++)
{
if (ballSetAssigned[i] == null)
{
Debug.Log($"{gameObject.name} reporting in: ballSetAssigned at index {i} is null. moving on");
continue;
}
ballSetAssigned[i] = gameData.ballSetAssigned[i];
}
}
}
GameData通过 SaveLoad 类保存可序列化数据的静态类
using UnityEngine;
[System.Serializable]
public class GameData
{
public ulong highestScore = 0;
public ulong totalCurrentCredits = 0;
public string playerName = " ";
public short ballSlotsUnlocked = 5;
public string[] ballSetAssigned = new string[20];
public GameData(Game game)
{
highestScore = game.highestScore;
totalCurrentCredits = game.totalCurrentCredits;
playerName = game.playerName;
ballSlotsUnlocked = game.ballSlotsUnlocked;
for (int i = 0; i < ballSetAssigned.Length; i++)
{
try
{
ballSetAssigned[i] = game.ballSetAssigned[i];
}
catch
{
Debug.Log($"null at {i}");
continue;
}
}
}
}
SaveLoad静态类负责序列化和反序列化数据。复制自 Brackeys。
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public static class SaveLoad
{
public static void SaveGame(Game game)
{
BinaryFormatter bf = new BinaryFormatter();
string path = Application.persistentDataPath + "/SavedGameData.gd";
FileStream file = new FileStream(path, FileMode.Create);
GameData gameData = new GameData(game);
bf.Serialize(file, gameData);
file.Close();
}
public static GameData LoadGame()
{
string path = Application.persistentDataPath + "/SavedGameData.gd";
if (File.Exists(path))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = new FileStream(path, FileMode.Open);
GameData gameData = (GameData)bf.Deserialize(file);
file.Close();
return gameData;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
EquipmentSlot 附加到加载场景中 UI 画布中的每个名为插槽的 GameObject。还附加到滚动视图的视口,用于将 DraggableItems 移动到装备和库存之间。
using System;
using UnityEngine;
using UnityEngine.EventSystems;
public class EquipmentSlot : MonoBehaviour, IDropHandler
{
public static string slotName;
Game game;
private void Awake()
{
if(!gameObject.name.Contains("View"))
{
game = GameObject.Find("GameDataManager").GetComponent<Game>();
game.slots.Add(gameObject);
}
}
public void OnDrop(PointerEventData eventData)
{
if(transform.childCount == 0)
{
try
{
GameObject dropped = eventData.pointerDrag;
DraggableItem draggableItem = dropped.GetComponent<DraggableItem>();
draggableItem.parentAfterDrag = transform;
}
catch
{
return;
}
}
else if(transform.name == "Viewport")
{
try
{
GameObject dropped = eventData.pointerDrag;
DraggableItem draggableItem = dropped.GetComponent<DraggableItem>();
Debug.Log(draggableItem.parentAfterDrag);
Debug.Log(transform);
draggableItem.parentAfterDrag = transform;
}
catch
{
return;
}
}
}
//Leftover from previous attempts, may be useful later.
int GetSlotIndex()
{
if(transform.name.Length == 13)
{
string indexCharacters = transform.name[transform.name.Length - 2].ToString() + transform.name[transform.name.Length - 1].ToString();
int index = Convert.ToInt32(indexCharacters);
return index;
}
else if(transform.name.Length == 12)
{
int index = Convert.ToInt32(transform.name[transform.name.Length - 1].ToString());
return index;
}
return 0;
}
public string GetBallName()
{
if(transform.childCount > 0)
{
return transform.GetComponentInChildren<DraggableItem>().ReferenceBallPathByUIName();
}
return null;
}
}
DraggableItem 附加到 UI 图像,表示要在游戏场景中实例化的游戏对象。
using UnityEngine.EventSystems;
using UnityEngine;
using UnityEngine.UI;
public class DraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
[HideInInspector] public Transform parentAfterDrag;
public Image image;
public string imageName;
public void OnBeginDrag(PointerEventData eventData)
{
parentAfterDrag = transform.parent;
transform.SetParent(transform.root);
transform.SetAsLastSibling();
image.raycastTarget = false;
imageName = gameObject.name;
}
public void OnDrag(PointerEventData eventData)
{
transform.position = Input.mousePosition;
}
public void OnEndDrag(PointerEventData eventData)
{
transform.SetParent(parentAfterDrag);
image.raycastTarget = true;
}
public string ReferenceBallPathByUIName()
{
switch(transform.name)
{
case "BombBallUIElement": return "Prefabs/Balls/Inactive/InactiveBombBall.prefab";
}
return null;
}
}
最初尝试序列化游戏对象数组,但 Unity 没有。然后,我将数组更改为字符串,并尝试了多种代码变体,以使 gameData 保存完整插槽中表示的数据并忽略空插槽。这就是我目前在不破坏 SaveLoad 系统的情况下最终得到的结果。
理想情况下,系统应该通过添加到游戏中字符串列表的设备脚本从插槽读取 UI 图像(在本例中为“BombBallUIElement”)。
然后将读取列表,如果任何引用返回一个球名称(即其在资源中的路径),则它将通过 Resources.Load(ballSetAssigned[index]) 保存到本地 GameObject 变量中,然后实例化。
首先,您尝试在
slots
Game
方法中访问尚未初始化的 Awake()
列表。
解决此问题的最简单方法是将声明更改为:
[HideInInspector] public List<GameObject> slots = new List<GameObject>();
我真的需要提一下,您在这里处理错误的方式并不是最好的。 你有 try catch 块,它输出如下内容:
Debug.Log($"null from Game Awake at index {i}");
假设 try 块中抛出的唯一错误将是空引用,但这不一定是真的。
您应该通过执行以下操作来输出实际错误:
catch (Exception e)
{
Debug.Log($"An error ocurred when assigning ball slots:\n{e}");
}
如果您确实想要针对空引用的情况有自定义消息,那么您可以检查
Exception
类型是否为 NullReferenceException
。
尽管您的消息是不必要的,因为异常本身会告诉您发生的位置,甚至将您链接到代码行。
对于序列化游戏对象数组,不幸的是,这不适用于内置的二进制格式化程序。您可以通过使用
JSON
或 XML
序列化来解决此问题。
但请记住,Unity 序列化
GameObject
的默认方式是存储 InstanceID
,这对于应用程序运行之间的持久数据不是很有用。
我知道有些人通过为对象生成 UUID 来解决这个问题,尽管我最近使用了一个将资产引用存储在
ScriptableObject
中并生成我自己的对象 ID 来序列化的系统。只是为了给你一些想法。
谈到你的第二点“SaveLoad 系统没有记录每个装载槽中的物品......”; 如果
slots
列表为 null,则它无法完全序列化,或者更确切地说,被序列化为空列表。
我希望这有帮助。如果我误解了什么,请告诉我。