事情是这样,最近用unity 转 微信小游戏发现 PlayerPrefs 转换后,对数据结构解析有问题,这个问题搜遍互联网没有人提到过,我是一点点的跟踪对比才发现的(对可能转小游戏类型,敏感度不够,哈哈),所以记录一下。
keyNotFoundException: Arg_KeyNotFoundWithKey, Level_1 at System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Level, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].get_Item(String ) at Carousel.Create() at Carousel.Start() UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object) UnityEngine.DebugLogHandler:LogException(Exception, Object) UnityEngine.Logger:LogException(Exception, Object) UnityEngine.Debug:LogException(Exception) UnityEngine.<>c:b__0_0(Object, UnhandledExceptionEventArgs) System.ThrowHelper:ThrowKeyNotFoundException(String) System.Collections.Generic.Dictionary`2:get_Item(String) Carousel:Create() Carousel:Start()
刚接触 untiy 的同学可能还不了解PlayerPrefs,简单介绍一下:
一、PlayerPrefs是什么?
PlayerPrefs是Unity3D提供的一个实用类,用于数据的本地持久化保存与读取。其工作原理简单直观,通过key-value形式将数据存储在本地设备上,方便开发者在代码中进行数据的写入、读取和更新操作。
二、PlayerPrefs有什么用?
PlayerPrefs在游戏开发中发挥着重要作用,尤其适用于单机游戏。它可以用来存储非关键性数据,如游戏存档、分数排名等。使用PlayerPrefs可以轻松实现这些数据的持久化存储,无需依赖服务器。
PlayerPrefs基本方法
-存
PlayerPrefs.SetInt("myAge", 1);
PlayerPrefs.SetFloat("myHeight", 1.1f);
PlayerPrefs.SetString("myName", "名字");
PlayerPrefs.Save();// 立即保存,防止崩溃未存上
-读
int age = PlayerPrefs.GetInt("myAge");
float height = PlayerPrefs.GetFloat("myHeight");
string name1 = PlayerPrefs.GetString("myName");
//重载方法:当传入的键错误或没有对应值时返回后面的参数
string name2 = PlayerPrefs.GetString("Name", "张麻子");
-查
//判断数据是否存在
if( PlayerPrefs.HasKey("myName") )
{
print("存在myName对应的键值对数据");
}
-删
//删除指定键值对
PlayerPrefs.DeleteKey("myAge");
//删除所有存储的信息
PlayerPrefs.DeleteAll();
我想用PlayerPrefs保存类似游戏关卡信息是这么写的:
首先定义要保存的数据类型:
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 某一关游戏的对象类型
[Serializable]
public class Level
{
public string name;
public int levelNum;
public int score = 0;
public bool isComplate = false;
// 这个要有,不然JsonMapper.ToObject(jsonStr) 会报错
public Level() { }
public Level(string name, int levelNum, int score, bool isComplate)
{
this.name = name;
this.levelNum = levelNum;
this.score = score;
this.isComplate = isComplate;
}
}
// 多个游戏关卡的集合
[Serializable]
public class Levels
{
// 字典类型
public Dictionary dic
= new Dictionary();
// 这个要有,不然JsonMapper.ToObject(jsonStr) 会报错
public Levels() { }
public Levels(Dictionary dic){
this.dic = dic;
}
}
然后定义存取类型对象的方法类:
public class LevelsJSON
{
Levels data;
public static string NAME = "Level_";
// 单例
public static LevelsJSON Instance { get; private set; }
private void Awake()
{
Instance = this;
}
public void Setup(int Total)
{
for (var i = 0; i <= Total; i++)
{
string name = NAME + i;
dic[name] = new Level(name, i, 0, false);
}
data = new Levels(dic);
string json = DataObjectToJson(data);
//保存数据
PlayerPrefs.SetString("Levels", json);
}
// 从playerPrefs读取数据,关键的地方通过反射可以完全还原出
// 之前的对象
public load(){
string json = PlayerPrefs.GetString("Levels");
Levels obj = DataJsonToObject(json);
return obj;
}
// json转对象
public T DataJsonToObject(string jsonStr)
{
T jsonObj = JsonMapper.ToObject(jsonStr); print(jsonObj);
return jsonObj;
}
//对象转json
public T DataJsonToObject2(string jsonStr)
{
T jsonObj = JsonMapper.ToObject(jsonStr);
return jsonObj;
}
}
重点来了:这套逻辑在untiy里没有问题,但是在微信小游戏里就有问题了。问题主要出在上面的数据结构还原成对象出错,保存在playerPrefs里是这样的:
{
"dic":{
"Level_0":{
"name":"Level_0",
"levelNum":"1",
"score":"0",
"isComplate":"false"
}
}
...// 此处类似略过
}
但实际应用读取json转自定义对象会有问题:
Levels levels = LevelsJSON.load();
Level level = levels.dic[0];// 这里小游戏会报错无法解析出Level
纠其原因应该是微信小游戏是自己封装的playerPrefs,映射不到unity 封装类的所有信息。
实际应该怎么解决:
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 某一关游戏的对象类型
[Serializable]
public class Level
{
public string name;
public int levelNum;
public int score = 0;
public bool isComplate = false;
// 这个要有,不然JsonMapper.ToObject(jsonStr) 会报错
public Level() { }
public Level(string name, int levelNum, int score, bool isComplate)
{
this.name = name;
this.levelNum = levelNum;
this.score = score;
this.isComplate = isComplate;
}
}
// 多个游戏关卡的集合
[Serializable]
public class Levels
{
// 数组类型
public List list;
// 这个要有,不然JsonMapper.ToObject(jsonStr) 会报错
public Levels() { }
public Levels(List list)
{
this.list = list;
}
}
public class LevelsJSON
{
Levels data;
public static string NAME = "Level_";
// 单例
public static LevelsJSON Instance { get; private set; }
private void Awake()
{
Instance = this;
}
public void Setup(int Total)
{
List levelList = new List(Total);
for (var i = 0; i <= Total; i++)
{
string name = NAME + i;
levelList.Add(new Level(name, i, 0, false));
}
data = new Levels(dic);
string json = DataObjectToJson(data);
//保存数据
PlayerPrefs.SetString("Levels", json);
}
// 从playerPrefs读取数据,关键的地方通过反射可以完全还原出
// 之前的对象
public load(){
string json = PlayerPrefs.GetString("Levels");
Levels obj = DataJsonToObject(json);
return obj;
}
// json转对象
public T DataJsonToObject(string jsonStr)
{
T jsonObj = JsonMapper.ToObject(jsonStr); print(jsonObj);
return jsonObj;
}
//对象转json
public T DataJsonToObject2(string jsonStr)
{
T jsonObj = JsonMapper.ToObject(jsonStr);
return jsonObj;
}
}
是的,把Levels 里的存储对象从字典Dictionary 换成 List 类型即可,unity 的数组对象和小游戏的数组对象,应该是做了应射关系,这样就能获取到了。
-----
研究unity 转 微信小游戏有段时间了,新的游戏也在审核之中。
对这块感兴趣的话,关注我的公众号就对啦!