【问题标题】:Dynamic loading of sprites from JSON file does not work从 JSON 文件动态加载精灵不起作用
【发布时间】:2019-07-11 07:37:26
【问题描述】:

我正在构建的 Unity 项目面向 iOS、Android 和 Windows X64。

问题
在我的一个场景中,我使用 JSON 文件在运行时动态加载位于 Resources 文件夹中的一些精灵。我目前遇到的问题是:当我在 Unity 编辑器中运行游戏时,它的行为符合预期(精灵被动态加载并显示在场景中)。但是当我在三个平台中的任何一个(在真实硬件上)运行它时,精灵不会加载/显示在场景中。但静态精灵已加载。

设置
场景是一种关卡选择屏幕。对于每个级别,都会显示一个精灵。显示的精灵和精灵数量基于场景开始时读取的 JSON 文件。这是一个截图,让您有更好的印象:

在其中一个游戏对象的 Startcallback 中,我运行代码来读取 JSON 数据:

var sceneSelectionInfoList = JsonHelper.GetSceneSelectionInfoForLanguage(GameLanguage.German);

目前为止的 JSONHelper 类(使用 JSON.NET 处理来自 Asset Store 的 Unity 资源):

using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public static class JsonHelper
{
    private const string SceneDataIndexFilename = "Assets/Resources/SceneData/SceneDataIndex.json";

    // Start is called before the first frame update
    public static List<SceneSelectionInfo> GetSceneSelectionInfoForLanguage(GameLanguage language)
    {
        var sceneSelectionInfoList = new List<SceneSelectionInfo>();

        // Open scene selection index
        var sceneDataIndexEntries = GetSceneDataIndexEntries(SceneDataIndexFilename);

        foreach (var sceneDataIndexEntry in sceneDataIndexEntries)
        {
            Logger.LogInfo(sceneDataIndexEntry.Filename);
            using (var streamReader = new StreamReader(sceneDataIndexEntry.Filename))
            {
                var jsonData = streamReader.ReadToEnd();
                var jObject = JObject.Parse(jsonData);
                var id = jObject.SelectToken("id").ToString();
                var basePath = jObject.SelectToken("basePath").ToString();
                var sceneSelectionImage = basePath + jObject.SelectToken("dragAndDrop.sceneSelectionImage");
                string title = null;

                switch (language)
                {
                    case GameLanguage.English:
                        title = jObject.SelectToken("titleEN").ToString();
                        break;
                    case GameLanguage.French:
                        title = jObject.SelectToken("titleFR").ToString();
                        break;
                    case GameLanguage.SwissGerman:
                        title = jObject.SelectToken("titleSG").ToString();
                        break;
                    case GameLanguage.Spanish:
                        title = jObject.SelectToken("titleES").ToString();
                        break;
                    case GameLanguage.German:
                        title = jObject.SelectToken("titleDE").ToString();
                        break;
                    case GameLanguage.Italian:
                        title = jObject.SelectToken("titleIT").ToString();
                        break;
                }

                var sceneSelectionInfo = new SceneSelectionInfo();
                sceneSelectionInfo.SceneId = id;
                sceneSelectionInfo.SceneSelectionImage = sceneSelectionImage;
                sceneSelectionInfo.Title = title;
                sceneSelectionInfoList.Add(sceneSelectionInfo);
            }
        }

        return sceneSelectionInfoList;
    }

    private static List<SceneDataIndexEntry> GetSceneDataIndexEntries(string sceneDataIndexFilename)
    {
        using (var reader = new StreamReader(sceneDataIndexFilename))
        {
            var jsonData = reader.ReadToEnd();
            Logger.LogInfo(jsonData);
            return JsonConvert.DeserializeObject<List<SceneDataIndexEntry>>(jsonData);
        }
    }
}

仅出于完整性考虑:SceneSelectionInfo 类只是一个数据容器 (DTO),其中包含一些要传递的值:

public class SceneSelectionInfo
{
    public string SceneId;
    public string SceneSelectionImage;
    public string Title;
}

这里是 JSON 文件和 sprite 的路径,相对于 Unity 项目文件夹:

精灵路径:
Assets/Resources/SceneData/AfternoonAtTheBeach/DragAndDrop/SceneSelection.png

JSON 文件路径:
Assets/Resources/SceneData/AfternoonAtTheBeach/SceneData.json

这是从 JSON 文件中截取的片段(basePathsceneSelectionImage 一起构建了要加载的精灵的路径):

{
  "id": "AfternoonAtTheBeach",
  "basePath": "SceneData/AfternoonAtTheBeach/",
  "titleEN": "Afternoon at the beach",
  "titleFR": "Après-midi sur la plage",
  "titleSG": "Namitag am Strand",
  "titleES": "Tarde en la playa",
  "titleDE": "Nachmittag am Strand",
  "titleIT": "Pomeriggio in spiaggia",
  "dragAndDrop": {
    "sceneSelectionImage": "DragAndDrop/SceneSelection",
    "levels": [
      {
        "backgroundImage": "DragAndDrop/Graphics/Level1/Background",
        "items": [
          {
            "image": "DragAndDrop/Graphics/Level1/Ball",
            "dropPosX": -623,

加载精灵的代码(从 JSON 文件读取路径后):

  var sprite = Resources.Load<Sprite>(sceneSelectionInfo.SceneSelectionImage);
  swiperItem.GetComponent<SpriteRenderer>().sprite = sprite;

到目前为止我检查过的内容

  • 我使用相对路径来引用 sprite,从 Assets/Resources 目录开始,没有文件扩展名(参见上面的 sprite 路径示例)。
  • 我已在 Unity Cloud Build 中禁用库缓存以避免旧的构建工件出现问题(因此每次构建时,我都会进行正确、干净的构建)
  • 我可以在本地构建所有三个平台(Unity 将其报告为“构建成功”)
  • 我正在使用 LoadSceneMode.Single(默认)
  • 我在本地和 Unity Cloud Build 中使用相同的 Unity 版本:2018.3.0f2

感谢任何提示!

【问题讨论】:

  • 这是一个复杂的问题,所以这里有一些你可以更新你的问题的东西:你的 JSON 文件以及你的 sprite 的完整路径是什么(与统一项目相关)?你加载 JSON 的代码是什么样的?在场景转换期间是否有任何复杂的异步操作或协程在运行?任何可能最终处于不良状态的静力学如何?初始化期间场景 A 依赖的任何东西(Awake()Start() 方法)?您使用的是LoadSceneMode.Single 还是LoadSceneMode.Additive?你试过切换到SceneManager.LoadSceneAsync()吗?
  • @Foggzie 感谢您的反馈。我在问题中添加了更多细节。我希望这能让它更清楚。关于协程:是的,我有一些(但没有一个是异步运行的)。查看示例代码。
  • 您是否认为 / 并不总是路径分隔符,应该是合适的?
  • @BugFinder 嗯,不,我没有。正斜杠是否存在任何已知问题?谢谢。
  • Windows 使用反斜杠 - 您始终可以使用 Path.PathSeparator 选择相关的反斜杠

标签: unity3d


【解决方案1】:

这里的两个主要问题是 1) JSON 文件的加载方式和 2) 管理器对象自行销毁的方式。


对于 JSON 文件:

您正在使用StreamReader,这是一个用于从文件系统读取的 C# 工具。这并不是 Unity 的“资源”系统的工作方式。由于它存在于“资产”目录下,您的编辑器可以找到它就好了;那是它在运行它的文件系统中存在的哪里。当您进行设备构建时,“资源”目录下的任何内容都会打包到构建中,并且必须通过Resources API 访问。

您有两个选择:您可以使用 Resources.Load&lt;TextAsset&gt; 调用替换您对 StreamReader 的使用,并确保使用“SceneData/SceneDataIndex”(注意:没有文件扩展名)而不是带有“资产/资源”的版本。您的另一个选择是将 JSON 资产放在名为“Assets/StreamingAssets”的折叠文件中,然后使用 Application.streamingAssetsPathStreamReader 加载它。流式资产路径允许使用常规 C# 文件加载约定加载它,因为它将放置在可读的文件系统路径中。

一些注意事项:Resources API 调用必须使用正斜杠 (/),无论平台如何。当使用像StreamReader这样的基于文件系统的加载器时,你应该使用Path.CombinePath.PathSeparator来确保你有正确的斜杠。


现在是场景 A 的第二次加载。如果不知道游戏对象层次结构的确切布局,很难确定您的问题是什么,但我的假设是您的 GameManager 脚本位于重要的 GameObject 上而你正在摧毁它:

else if (Instance != this)
{
    Destroy(gameObject);
}

这将销毁游戏对象、其上的所有脚本、及其所有子对象以及它们上的所有脚本。如果您将其更改为 Destroy(this),它只会从对象中删除脚本并保持游戏对象层次结构不变。

【讨论】:

  • 非常感谢您的详细回复。事实上,您关于 JSON 和 Resources.Load 的提示解决了我缺少精灵的问题。所以这很好。惊人的!我目前正在调查第二个问题。随时通知您。
  • 如果我将未加载到单独的 SO 问题中的性问题(场景 A)移动,您可以吗?之后,您只需将答案部分移至新的部分。最后,我可以在这里将您的答案标记为正确。
  • 好的,我已经把问题分开了。这是另一个 SO 问题的链接。把你的那部分帖子移到那里。还在问题描述中添加了更多细节。 stackoverflow.com/questions/54775006/… 非常感谢!
猜你喜欢
  • 2021-07-01
  • 1970-01-01
  • 2016-04-07
  • 2014-11-22
  • 1970-01-01
  • 1970-01-01
  • 2017-03-13
  • 1970-01-01
相关资源
最近更新 更多