You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity弹珠游戏跨场景UI装备槽实例化与存档问题求助

弹珠台游戏装备配置与存档加载问题

问题概述

我开发了一款弹珠台(Pachinko)风格的游戏,支持玩家在进入游戏场景前,通过独立的装备配置(Loadout)场景创建自定义弹珠组合。目前遇到两个核心问题:

  • 引用返回为空,导致进入游戏场景时触发空引用错误崩溃
  • 存档加载(SaveLoad)系统无法记录每个装备槽中的物品

Game脚本(挂载在每个场景的空GameObject上)

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脚本(用于存储可序列化数据)

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 Canvas的每个slot GameObject及滚动视图Viewport上,处理拖拽逻辑)

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;
            }
        }
    }

    //之前尝试留下的代码,后续可能有用
    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;
    }
}

背景与理想逻辑

最初尝试序列化GameObject数组,但Unity不支持该操作,因此改为字符串类型存储弹珠资源路径。当前代码是在不破坏存档加载系统前提下的最终版本,期望实现的逻辑是:

  • 通过EquipmentSlot脚本读取装备槽中的UI元素(如"BombBallUIElement"),将对应的弹珠资源路径添加至Game类的字符串数组
  • 读取该数组时,若路径有效,则通过Resources.Load(ballSetAssigned[index])加载并实例化弹珠

内容的提问来源于stack exchange,提问作者Kevin Will

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 09:22:04