[TASK] Show collected duck stickers

This commit is contained in:
2019-08-24 00:31:21 +02:00
parent 279c5fbbbe
commit c8ab81a848
43 changed files with 4969 additions and 97 deletions

View File

@@ -7,6 +7,8 @@ public class CollectableDuck: MonoBehaviour
{ {
private Camera _camera; private Camera _camera;
private Collider _collider; private Collider _collider;
public CollectableDuckData CollectableDuckData { get; set; }
private void Awake() private void Awake()
{ {
@@ -14,6 +16,7 @@ public class CollectableDuck: MonoBehaviour
_collider = GetComponent<Collider>(); _collider = GetComponent<Collider>();
} }
private void Update() private void Update()
{ {
if (Input.GetMouseButtonUp(0)) if (Input.GetMouseButtonUp(0))
@@ -22,7 +25,11 @@ public class CollectableDuck: MonoBehaviour
RaycastHit hit;; RaycastHit hit;;
if (Physics.Raycast(ray, out hit) && hit.collider == _collider) if (Physics.Raycast(ray, out hit) && hit.collider == _collider)
{ {
Debug.Log("Tapped a ducky"); if (CollectableDuckData == null)
{
throw new Exception("Unknown duck clicked in '" + gameObject.name + "'");
}
CollectableDuckManager.Instance.DuckCollected(CollectableDuckData);
} }
} }
} }

View File

@@ -2,12 +2,17 @@ using System;
using System.Globalization; using System.Globalization;
using Mapbox.Unity.Utilities; using Mapbox.Unity.Utilities;
using Mapbox.Utils; using Mapbox.Utils;
using Unity.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
[CreateAssetMenu(fileName = "duck", menuName = "Duck/Collectable", order = 0)] [CreateAssetMenu(fileName = "duck", menuName = "Duck/Collectable", order = 0)]
public class CollectableDuckData: ScriptableObject public class CollectableDuckData: ScriptableObject
{ {
[SerializeField]
[ReadOnly]
private string _id = System.Guid.NewGuid().ToString();
[SerializeField] [SerializeField]
[Geocode] [Geocode]
private string _latitudeLongitude; private string _latitudeLongitude;
@@ -38,6 +43,8 @@ public class CollectableDuckData: ScriptableObject
} }
} }
public string Id => _id;
public GameObject ModelPrefab => _modelPrefab; public GameObject ModelPrefab => _modelPrefab;
public DuckStickerData StickerData => stickerData; public DuckStickerData StickerData => stickerData;

View File

@@ -1,16 +1,17 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Mapbox.CheapRulerCs; using Mapbox.CheapRulerCs;
using Mapbox.Unity.Map; using Mapbox.Unity.Map;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
public class CollectableDuckManager : MonoBehaviour public class CollectableDuckManager : MonoBehaviour
{ {
[FormerlySerializedAs("CollectableDucks")] public List<CollectableDuckData> collectableDucks; public List<CollectableDuckData> collectableDucks;
Dictionary<CollectableDuckData, CollectableDuck> spawnedDucks = new Dictionary<CollectableDuckData, CollectableDuck>(); private readonly HashSet<CollectableDuckData> _collectedDucks = new HashSet<CollectableDuckData>();
private readonly Dictionary<CollectableDuckData, CollectableDuck> _spawnedDucks = new Dictionary<CollectableDuckData, CollectableDuck>();
public AbstractMap map; public AbstractMap map;
public Transform player; public Transform player;
@@ -34,6 +35,15 @@ public class CollectableDuckManager : MonoBehaviour
Instance = this; Instance = this;
} }
private void Awake()
{
_collectedDucks.Clear();
foreach (var duck in collectableDucks.Where(duck => PlayerPrefs.GetInt("duck." + duck.Id) > 0))
{
_collectedDucks.Add(duck);
}
}
private void Update() private void Update()
{ {
var ruler = new CheapRuler(map.CenterLatitudeLongitude.x, CheapRulerUnits.Meters); var ruler = new CheapRuler(map.CenterLatitudeLongitude.x, CheapRulerUnits.Meters);
@@ -44,33 +54,58 @@ public class CollectableDuckManager : MonoBehaviour
var duckCoord = duck.LatitudeLongitude; var duckCoord = duck.LatitudeLongitude;
var dist = ruler.Distance(duckCoord.ToArray(), playerPosition); var dist = ruler.Distance(duckCoord.ToArray(), playerPosition);
// Debug.Log("Distance to " + duck.name + ": " + dist); // Debug.Log("Distance to " + duck.name + ": " + dist);
if (dist < spawnDistance) if (dist < spawnDistance && !_collectedDucks.Contains(duck))
{ {
if (!spawnedDucks.ContainsKey(duck)) SpawnDuck(duck);
{ }
// Spawn the duck else
var duckSpawn = Instantiate(duckSpawnPrefab, map.GeoToWorldPosition(duckCoord), Quaternion.identity,
transform);
var duckModel = Instantiate(duck.ModelPrefab, duckSpawn.transform);
duckModel.transform.localPosition = Vector3.zero;
spawnedDucks[duck] = duckSpawn;
}
else
{
if (!spawnedDucks[duck].gameObject.activeSelf)
{
spawnedDucks[duck].gameObject.SetActive(true);
}
}
} else if (spawnedDucks.ContainsKey(duck))
{ {
if (spawnedDucks[duck].gameObject.activeSelf) RemoveDuck(duck);
{
spawnedDucks[duck].gameObject.SetActive(false);
}
} }
} }
} }
private void SpawnDuck(CollectableDuckData duck)
{
if (!_spawnedDucks.ContainsKey(duck))
{
// Spawn the duck
var duckSpawn = Instantiate(duckSpawnPrefab, map.GeoToWorldPosition(duck.LatitudeLongitude),
Quaternion.identity, transform);
duckSpawn.CollectableDuckData = duck;
var duckModel = Instantiate(duck.ModelPrefab, duckSpawn.transform);
duckModel.transform.localPosition = Vector3.zero;
_spawnedDucks[duck] = duckSpawn;
}
else
{
if (!_spawnedDucks[duck].gameObject.activeSelf)
{
_spawnedDucks[duck].gameObject.SetActive(true);
}
}
}
private void RemoveDuck(CollectableDuckData duck)
{
if (_spawnedDucks.ContainsKey(duck))
{
if (_spawnedDucks[duck].gameObject.activeSelf)
{
_spawnedDucks[duck].gameObject.SetActive(false);
}
}
}
public void DuckCollected(CollectableDuckData duck)
{
_collectedDucks.Add(duck);
PlayerPrefs.SetInt("duck." + duck.Id, 1);
DuckStickerManager.Instance.OnStickerCollected(duck.StickerData);
RemoveDuck(duck);
}
} }

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollectedStickersUI : MonoBehaviour
{
public Transform collectedStickerParent;
public StickerDisplay stickerDisplayPrefab;
private readonly Dictionary<DuckStickerData, StickerDisplay> _stickerDisplays = new Dictionary<DuckStickerData, StickerDisplay>();
private void Start()
{
RefreshStickerDisplay();
}
public void OnStickerCollected(DuckStickerData sticker)
{
RefreshStickerDisplay();
}
private void RefreshStickerDisplay()
{
var collectedStickers = DuckStickerManager.Instance.CollectedStickers;
foreach (var sticker in collectedStickers)
{
if (!_stickerDisplays.ContainsKey(sticker))
{
var stickerDisplay = Instantiate(stickerDisplayPrefab, collectedStickerParent);
stickerDisplay.Sticker = sticker;
_stickerDisplays.Add(sticker, stickerDisplay);
}
_stickerDisplays[sticker].CollectedCount = DuckStickerManager.Instance.GetStickerCollectedCount(sticker);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a3648f0924e57e45a76b20f88dfa169
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,15 +1,23 @@
using System;
using Unity.Collections;
using UnityEngine; using UnityEngine;
[CreateAssetMenu(fileName = "sticker", menuName = "Duck/Sticker", order = 10)] [CreateAssetMenu(fileName = "sticker", menuName = "Duck/Sticker", order = 10)]
[Serializable]
public class DuckStickerData: ScriptableObject public class DuckStickerData: ScriptableObject
{ {
[SerializeField]
[ReadOnly]
private string _id = System.Guid.NewGuid().ToString();
[SerializeField] [SerializeField]
private string _label; private string _label;
[SerializeField] [SerializeField]
private Sprite _stickerSprite; private Sprite _stickerSprite;
public string Id => _id;
public string Label => _label; public string Label => _label;
public Sprite StickerSprite => _stickerSprite; public Sprite StickerSprite => _stickerSprite;

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
[Serializable]
public class DuckStickerEvent : UnityEvent<DuckStickerData>
{
}
public class DuckStickerManager : MonoBehaviour
{
public List<DuckStickerData> duckStickers;
public DuckStickerEvent stickerCollected;
private readonly Dictionary<DuckStickerData, int> _collectedStickers = new Dictionary<DuckStickerData, int>();
private static DuckStickerManager _instance;
public static DuckStickerManager Instance
{
get { return _instance; }
private set { _instance = value; }
}
public IEnumerable<DuckStickerData> CollectedStickers
{
get { return duckStickers.Where(d => GetStickerCollectedCount(d) > 0); }
}
public DuckStickerManager()
{
Instance = this;
}
private void Awake()
{
foreach (var sticker in duckStickers)
{
_collectedStickers.Add(sticker, PlayerPrefs.GetInt("sticker." + sticker.Id));
}
}
public void OnStickerCollected(DuckStickerData sticker)
{
_collectedStickers[sticker]++;
PlayerPrefs.SetInt("sticker." + sticker.Id, _collectedStickers[sticker]);
stickerCollected.Invoke(sticker);
}
public int GetStickerCollectedCount(DuckStickerData sticker)
{
return _collectedStickers.ContainsKey(sticker) ? _collectedStickers[sticker] : 0;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 538584e4143975140a6b20eedd320890
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9e5dde5edc982ab469f4c758687419e9 guid: cd80db7a238136242b05260cf97525a5
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 81f0be54599e19245a63a2d494e64cb8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1b15603e344fd7447ad6eede3ff52c67
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,966 @@
//
// PlistCS Property List (plist) serialization and parsing library.
//
// https://github.com/animetrics/PlistCS
//
// Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Sabresaurus Note: parseBinaryReal has been modified from its state, please see the method's code for details
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
namespace Sabresaurus.PlayerPrefsExtensions
{
public static class Plist
{
private static List<int> offsetTable = new List<int>();
private static List<byte> objectTable = new List<byte>();
private static int refCount;
private static int objRefSize;
private static int offsetByteSize;
private static long offsetTableOffset;
#region Public Functions
public static object readPlist(string path)
{
using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read))
{
return readPlist(f, plistType.Auto);
}
}
public static object readPlistSource(string source)
{
return readPlist(System.Text.Encoding.UTF8.GetBytes(source));
}
public static object readPlist(byte[] data)
{
return readPlist(new MemoryStream(data), plistType.Auto);
}
public static plistType getPlistType(Stream stream)
{
byte[] magicHeader = new byte[8];
stream.Read(magicHeader, 0, 8);
if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810)
{
return plistType.Binary;
}
else
{
return plistType.Xml;
}
}
public static object readPlist(Stream stream, plistType type)
{
if (type == plistType.Auto)
{
type = getPlistType(stream);
stream.Seek(0, SeekOrigin.Begin);
}
if (type == plistType.Binary)
{
using (BinaryReader reader = new BinaryReader(stream))
{
byte[] data = reader.ReadBytes((int) reader.BaseStream.Length);
return readBinary(data);
}
}
else
{
XmlDocument xml = new XmlDocument();
xml.XmlResolver = null;
xml.Load(stream);
return readXml(xml);
}
}
public static void writeXml(object value, string path)
{
using (StreamWriter writer = new StreamWriter(path))
{
writer.Write(writeXml(value));
}
}
public static void writeXml(object value, Stream stream)
{
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(writeXml(value));
}
}
public static string writeXml(object value)
{
using (MemoryStream ms = new MemoryStream())
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false);
xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document;
xmlWriterSettings.Indent = true;
using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings))
{
xmlWriter.WriteStartDocument();
//xmlWriter.WriteComment("DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"");
xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
xmlWriter.WriteStartElement("plist");
xmlWriter.WriteAttributeString("version", "1.0");
compose(value, xmlWriter);
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
xmlWriter.Flush();
xmlWriter.Close();
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
}
}
}
public static void writeBinary(object value, string path)
{
using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create)))
{
writer.Write(writeBinary(value));
}
}
public static void writeBinary(object value, Stream stream)
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(writeBinary(value));
}
}
public static byte[] writeBinary(object value)
{
offsetTable.Clear();
objectTable.Clear();
refCount = 0;
objRefSize = 0;
offsetByteSize = 0;
offsetTableOffset = 0;
//Do not count the root node, subtract by 1
int totalRefs = countObject(value) - 1;
refCount = totalRefs;
objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length;
composeBinary(value);
writeBinaryString("bplist00", false);
offsetTableOffset = (long)objectTable.Count;
offsetTable.Add(objectTable.Count - 8);
offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count-1])).Length;
List<byte> offsetBytes = new List<byte>();
offsetTable.Reverse();
for (int i = 0; i < offsetTable.Count; i++)
{
offsetTable[i] = objectTable.Count - offsetTable[i];
byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize);
Array.Reverse(buffer);
offsetBytes.AddRange(buffer);
}
objectTable.AddRange(offsetBytes);
objectTable.AddRange(new byte[6]);
objectTable.Add(Convert.ToByte(offsetByteSize));
objectTable.Add(Convert.ToByte(objRefSize));
var a = BitConverter.GetBytes((long) totalRefs + 1);
Array.Reverse(a);
objectTable.AddRange(a);
objectTable.AddRange(BitConverter.GetBytes((long)0));
a = BitConverter.GetBytes(offsetTableOffset);
Array.Reverse(a);
objectTable.AddRange(a);
return objectTable.ToArray();
}
#endregion
#region Private Functions
private static object readXml(XmlDocument xml)
{
XmlNode rootNode = xml.DocumentElement.ChildNodes[0];
return parse(rootNode);
}
private static object readBinary(byte[] data)
{
offsetTable.Clear();
List<byte> offsetTableBytes = new List<byte>();
objectTable.Clear();
refCount = 0;
objRefSize = 0;
offsetByteSize = 0;
offsetTableOffset = 0;
List<byte> bList = new List<byte>(data);
List<byte> trailer = bList.GetRange(bList.Count - 32, 32);
parseTrailer(trailer);
objectTable = bList.GetRange(0, (int)offsetTableOffset);
offsetTableBytes = bList.GetRange((int)offsetTableOffset, bList.Count - (int)offsetTableOffset - 32);
parseOffsetTable(offsetTableBytes);
return parseBinary(0);
}
private static Dictionary<string, object> parseDictionary(XmlNode node)
{
XmlNodeList children = node.ChildNodes;
if (children.Count % 2 != 0)
{
throw new DataMisalignedException("Dictionary elements must have an even number of child nodes");
}
Dictionary<string, object> dict = new Dictionary<string, object>();
for (int i = 0; i < children.Count; i += 2)
{
XmlNode keynode = children[i];
XmlNode valnode = children[i + 1];
if (keynode.Name != "key")
{
throw new ApplicationException("expected a key node");
}
object result = parse(valnode);
if (result != null)
{
dict.Add(keynode.InnerText, result);
}
}
return dict;
}
private static List<object> parseArray(XmlNode node)
{
List<object> array = new List<object>();
foreach (XmlNode child in node.ChildNodes)
{
object result = parse(child);
if (result != null)
{
array.Add(result);
}
}
return array;
}
private static void composeArray(List<object> value, XmlWriter writer)
{
writer.WriteStartElement("array");
foreach (object obj in value)
{
compose(obj, writer);
}
writer.WriteEndElement();
}
private static object parse(XmlNode node)
{
switch (node.Name)
{
case "dict":
return parseDictionary(node);
case "array":
return parseArray(node);
case "string":
return node.InnerText;
case "integer":
// int result;
//int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result);
return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo);
case "real":
return Convert.ToDouble(node.InnerText,System.Globalization.NumberFormatInfo.InvariantInfo);
case "false":
return false;
case "true":
return true;
case "null":
return null;
case "date":
return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc);
case "data":
return Convert.FromBase64String(node.InnerText);
}
throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name));
}
private static void compose(object value, XmlWriter writer)
{
if (value == null || value is string)
{
writer.WriteElementString("string", value as string);
}
else if (value is int || value is long)
{
writer.WriteElementString("integer", ((int)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
}
else if (value is System.Collections.Generic.Dictionary<string, object> ||
value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String"))
{
//Convert to Dictionary<string, object>
Dictionary<string, object> dic = value as Dictionary<string, object>;
if (dic == null)
{
dic = new Dictionary<string, object>();
IDictionary idic = (IDictionary)value;
foreach (var key in idic.Keys)
{
dic.Add(key.ToString(), idic[key]);
}
}
writeDictionaryValues(dic, writer);
}
else if (value is List<object>)
{
composeArray((List<object>)value, writer);
}
else if (value is byte[])
{
writer.WriteElementString("data", Convert.ToBase64String((Byte[])value));
}
else if (value is float || value is double)
{
writer.WriteElementString("real", ((double)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
}
else if (value is DateTime)
{
DateTime time = (DateTime)value;
string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc);
writer.WriteElementString("date", theString);//, "yyyy-MM-ddTHH:mm:ssZ"));
}
else if (value is bool)
{
writer.WriteElementString(value.ToString().ToLower(), "");
}
else
{
throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString()));
}
}
private static void writeDictionaryValues(Dictionary<string, object> dictionary, XmlWriter writer)
{
writer.WriteStartElement("dict");
foreach (string key in dictionary.Keys)
{
object value = dictionary[key];
writer.WriteElementString("key", key);
compose(value, writer);
}
writer.WriteEndElement();
}
private static int countObject(object value)
{
int count = 0;
switch (value.GetType().ToString())
{
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
Dictionary<string, object> dict = (Dictionary<string, object>)value;
foreach (string key in dict.Keys)
{
count += countObject(dict[key]);
}
count += dict.Keys.Count;
count++;
break;
case "System.Collections.Generic.List`1[System.Object]":
List<object> list = (List<object>)value;
foreach (object obj in list)
{
count += countObject(obj);
}
count++;
break;
default:
count++;
break;
}
return count;
}
private static byte[] writeBinaryDictionary(Dictionary<string, object> dictionary)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
List<int> refs = new List<int>();
for (int i = dictionary.Count - 1; i >= 0; i--)
{
var o = new object[dictionary.Count];
dictionary.Values.CopyTo(o, 0);
composeBinary(o[i]);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
for (int i = dictionary.Count - 1; i >= 0; i--)
{
var o = new string[dictionary.Count];
dictionary.Keys.CopyTo(o, 0);
composeBinary(o[i]);//);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
if (dictionary.Count < 15)
{
header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count)));
}
else
{
header.Add(0xD0 | 0xf);
header.AddRange(writeBinaryInteger(dictionary.Count, false));
}
foreach (int val in refs)
{
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
Array.Reverse(refBuffer);
buffer.InsertRange(0, refBuffer);
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] composeBinaryArray(List<object> objects)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
List<int> refs = new List<int>();
for (int i = objects.Count - 1; i >= 0; i--)
{
composeBinary(objects[i]);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
if (objects.Count < 15)
{
header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count)));
}
else
{
header.Add(0xA0 | 0xf);
header.AddRange(writeBinaryInteger(objects.Count, false));
}
foreach (int val in refs)
{
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
Array.Reverse(refBuffer);
buffer.InsertRange(0, refBuffer);
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] composeBinary(object obj)
{
byte[] value;
switch (obj.GetType().ToString())
{
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
value = writeBinaryDictionary((Dictionary<string, object>)obj);
return value;
case "System.Collections.Generic.List`1[System.Object]":
value = composeBinaryArray((List<object>)obj);
return value;
case "System.Byte[]":
value = writeBinaryByteArray((byte[])obj);
return value;
case "System.Double":
value = writeBinaryDouble((double)obj);
return value;
case "System.Int32":
value = writeBinaryInteger((int)obj, true);
return value;
case "System.String":
value = writeBinaryString((string)obj, true);
return value;
case "System.DateTime":
value = writeBinaryDate((DateTime)obj);
return value;
case "System.Boolean":
value = writeBinaryBool((bool)obj);
return value;
default:
return new byte[0];
}
}
public static byte[] writeBinaryDate(DateTime obj)
{
List<byte> buffer =new List<byte>(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), 8));
buffer.Reverse();
buffer.Insert(0, 0x33);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
public static byte[] writeBinaryBool(bool obj)
{
List<byte> buffer = new List<byte>(new byte[1] { (bool)obj ? (byte)9 : (byte)8 });
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryInteger(int value, bool write)
{
List<byte> buffer = new List<byte>(BitConverter.GetBytes((long) value));
buffer =new List<byte>(RegulateNullBytes(buffer.ToArray()));
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
buffer.Add(0);
int header = 0x10 | (int)(Math.Log(buffer.Count) / Math.Log(2));
buffer.Reverse();
buffer.Insert(0, Convert.ToByte(header));
if (write)
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryDouble(double value)
{
List<byte> buffer =new List<byte>(RegulateNullBytes(BitConverter.GetBytes(value), 4));
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
buffer.Add(0);
int header = 0x20 | (int)(Math.Log(buffer.Count) / Math.Log(2));
buffer.Reverse();
buffer.Insert(0, Convert.ToByte(header));
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryByteArray(byte[] value)
{
List<byte> buffer = new List<byte>(value);
List<byte> header = new List<byte>();
if (value.Length < 15)
{
header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length)));
}
else
{
header.Add(0x40 | 0xf);
header.AddRange(writeBinaryInteger(buffer.Count, false));
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryString(string value, bool head)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
foreach (char chr in value.ToCharArray())
buffer.Add(Convert.ToByte(chr));
if (head)
{
if (value.Length < 15)
{
header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length)));
}
else
{
header.Add(0x50 | 0xf);
header.AddRange(writeBinaryInteger(buffer.Count, false));
}
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] RegulateNullBytes(byte[] value)
{
return RegulateNullBytes(value, 1);
}
private static byte[] RegulateNullBytes(byte[] value, int minBytes)
{
Array.Reverse(value);
List<byte> bytes = new List<byte>(value);
for (int i = 0; i < bytes.Count; i++)
{
if (bytes[i] == 0 && bytes.Count > minBytes)
{
bytes.Remove(bytes[i]);
i--;
}
else
break;
}
if (bytes.Count < minBytes)
{
int dist = minBytes - bytes.Count;
for (int i = 0; i < dist; i++)
bytes.Insert(0, 0);
}
value = bytes.ToArray();
Array.Reverse(value);
return value;
}
private static void parseTrailer(List<byte> trailer)
{
offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0);
objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0);
byte[] refCountBytes = trailer.GetRange(12, 4).ToArray();
Array.Reverse(refCountBytes);
refCount = BitConverter.ToInt32(refCountBytes, 0);
byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray();
Array.Reverse(offsetTableOffsetBytes);
offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0);
}
private static void parseOffsetTable(List<byte> offsetTableBytes)
{
for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize)
{
byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray();
Array.Reverse(buffer);
offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0));
}
}
private static object parseBinaryDictionary(int objRef)
{
Dictionary<string, object> buffer = new Dictionary<string, object>();
List<int> refs = new List<int>();
int refCount = 0;
int refStartPosition;
refCount = getCount(offsetTable[objRef], out refStartPosition);
if (refCount < 15)
refStartPosition = offsetTable[objRef] + 1;
else
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize)
{
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
Array.Reverse(refBuffer);
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
}
for (int i = 0; i < refCount; i++)
{
buffer.Add((string)parseBinary(refs[i]), parseBinary(refs[i + refCount]));
}
return buffer;
}
private static object parseBinaryArray(int objRef)
{
List<object> buffer = new List<object>();
List<int> refs = new List<int>();
int refCount = 0;
int refStartPosition;
refCount = getCount(offsetTable[objRef], out refStartPosition);
if (refCount < 15)
refStartPosition = offsetTable[objRef] + 1;
else
//The following integer has a header aswell so we increase the refStartPosition by two to account for that.
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize)
{
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
Array.Reverse(refBuffer);
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
}
for (int i = 0; i < refCount; i++)
{
buffer.Add(parseBinary(refs[i]));
}
return buffer;
}
private static int getCount(int bytePosition, out int newBytePosition)
{
byte headerByte = objectTable[bytePosition];
byte headerByteTrail = Convert.ToByte(headerByte & 0xf);
int count;
if (headerByteTrail < 15)
{
count = headerByteTrail;
newBytePosition = bytePosition + 1;
}
else
count = (int)parseBinaryInt(bytePosition + 1, out newBytePosition);
return count;
}
private static object parseBinary(int objRef)
{
byte header = objectTable[offsetTable[objRef]];
switch (header & 0xF0)
{
case 0:
{
//If the byte is
//0 return null
//9 return true
//8 return false
return (objectTable[offsetTable[objRef]] == 0) ? (object)null : ((objectTable[offsetTable[objRef]] == 9) ? true : false);
}
case 0x10:
{
return parseBinaryInt(offsetTable[objRef]);
}
case 0x20:
{
return parseBinaryReal(offsetTable[objRef]);
}
case 0x30:
{
return parseBinaryDate(offsetTable[objRef]);
}
case 0x40:
{
return parseBinaryByteArray(offsetTable[objRef]);
}
case 0x50://String ASCII
{
return parseBinaryAsciiString(offsetTable[objRef]);
}
case 0x60://String Unicode
{
return parseBinaryUnicodeString(offsetTable[objRef]);
}
case 0xD0:
{
return parseBinaryDictionary(objRef);
}
case 0xA0:
{
return parseBinaryArray(objRef);
}
}
throw new Exception("This type is not supported");
}
public static object parseBinaryDate(int headerPosition)
{
byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray();
Array.Reverse(buffer);
double appleTime = BitConverter.ToDouble(buffer, 0);
DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime);
return result;
}
private static object parseBinaryInt(int headerPosition)
{
int output;
return parseBinaryInt(headerPosition, out output);
}
private static object parseBinaryInt(int headerPosition, out int newHeaderPosition)
{
byte header = objectTable[headerPosition];
int byteCount = (int)Math.Pow(2, header & 0xf);
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
Array.Reverse(buffer);
//Add one to account for the header byte
newHeaderPosition = headerPosition + byteCount + 1;
return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0);
}
private static object parseBinaryReal(int headerPosition)
{
byte header = objectTable[headerPosition];
int byteCount = (int)Math.Pow(2, header & 0xf);
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
Array.Reverse(buffer);
// Sabresaurus Note: This wasn't producing the right results with doubles, needed singles anyway, so I
// added this line. (original line is commented out)
return BitConverter.ToSingle(buffer, 0);
// return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0);
}
private static object parseBinaryAsciiString(int headerPosition)
{
int charStartPosition;
int charCount = getCount(headerPosition, out charStartPosition);
var buffer = objectTable.GetRange(charStartPosition, charCount);
return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty;
}
private static object parseBinaryUnicodeString(int headerPosition)
{
int charStartPosition;
int charCount = getCount(headerPosition, out charStartPosition);
charCount = charCount * 2;
byte[] buffer = new byte[charCount];
byte one, two;
for (int i = 0; i < charCount; i+=2)
{
one = objectTable.GetRange(charStartPosition+i,1)[0];
two = objectTable.GetRange(charStartPosition + i+1, 1)[0];
if (BitConverter.IsLittleEndian)
{
buffer[i] = two;
buffer[i + 1] = one;
}
else
{
buffer[i] = one;
buffer[i + 1] = two;
}
}
return Encoding.Unicode.GetString(buffer);
}
private static object parseBinaryByteArray(int headerPosition)
{
int byteStartPosition;
int byteCount = getCount(headerPosition, out byteStartPosition);
return objectTable.GetRange(byteStartPosition, byteCount).ToArray();
}
#endregion
}
public enum plistType
{
Auto, Binary, Xml
}
public static class PlistDateConverter
{
public static long timeDifference = 978307200;
public static long GetAppleTime(long unixTime)
{
return unixTime - timeDifference;
}
public static long GetUnixTime(long appleTime)
{
return appleTime + timeDifference;
}
public static DateTime ConvertFromAppleTimeStamp(double timestamp)
{
DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
return origin.AddSeconds(timestamp);
}
public static double ConvertToAppleTimeStamp(DateTime date)
{
DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
TimeSpan diff = date - begin;
return Math.Floor(diff.TotalSeconds);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69fd4b81f600d46008cd7a0740a37348
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,955 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using Sabresaurus.PlayerPrefsExtensions;
public class PlayerPrefsEditor : EditorWindow
{
// Represents a PlayerPref key-value record
[Serializable]
private struct PlayerPrefPair
{
public string Key
{
get;
set;
}
public object Value
{
get;
set;
}
}
readonly DateTime MISSING_DATETIME = new DateTime(1601,1,1);
// Natively player prefs can be one of these three types
enum PlayerPrefType { Float = 0, Int, String };
// The actual cached store of player pref records fetched from registry or plist
List<PlayerPrefPair> deserializedPlayerPrefs = new List<PlayerPrefPair>();
// When a search is in effect the search results are cached in this list
List<PlayerPrefPair> filteredPlayerPrefs = new List<PlayerPrefPair>();
// Track last successful deserialisation to prevent doing this too often. On OSX this uses the player prefs file
// last modified time, on Windows we just poll repeatedly and use this to prevent polling again too soon.
DateTime? lastDeserialization = null;
// The view position of the player prefs scroll view
Vector2 scrollPosition;
// The scroll position from last frame (used with scrollPosition to detect user scrolling)
Vector2 lastScrollPosition;
// Prevent OnInspector() forcing a repaint every time it's called
int inspectorUpdateFrame = 0;
// Automatically attempt to decrypt keys and values that are detected as encrypted
bool automaticDecryption = false;
// Filter the keys by search
string searchFilter = string.Empty;
// Because of some issues with deleting from OnGUI, we defer it to OnInspectorUpdate() instead
string keyQueuedForDeletion = null;
// Company and product name for importing player prefs from other projects
string importCompanyName = "";
string importProductName = "";
#region Adding New PlayerPref
// This is the current type of player pref that the user is about to create
PlayerPrefType newEntryType = PlayerPrefType.String;
// Whether the player pref should be encrypted
bool newEntryIsEncrypted = false;
// The identifier of the new player pref
string newEntryKey = "";
// Value of the player pref about to be created (must be tracked differently for each type)
float newEntryValueFloat = 0;
int newEntryValueInt = 0;
string newEntryValueString = "";
#endregion
[MenuItem("Window/Player Prefs Editor")]
private static void Init()
{
// Get existing open window or if none, make a new one:
PlayerPrefsEditor editor = (PlayerPrefsEditor)EditorWindow.GetWindow(typeof(PlayerPrefsEditor));
// Require the editor window to be at least 300 pixels wide
Vector2 minSize = editor.minSize;
minSize.x = 230;
editor.minSize = minSize;
editor.importCompanyName = PlayerSettings.companyName;
editor.importProductName = PlayerSettings.productName;
}
/// <summary>
/// This returns an array of the stored PlayerPrefs from the file system (OSX) or registry (Windows), to allow
/// us to to look up what's actually in the PlayerPrefs. This is used as a kind of lookup table.
/// </summary>
private PlayerPrefPair[] RetrieveSavedPrefs(string companyName, string productName)
{
if(Application.platform == RuntimePlatform.OSXEditor)
{
// From Unity docs: On Mac OS X PlayerPrefs are stored in ~/Library/Preferences folder, in a file named unity.[company name].[product name].plist, where company and product names are the names set up in Project Settings. The same .plist file is used for both Projects run in the Editor and standalone players.
// Construct the plist filename from the project's settings
string plistFilename = string.Format("unity.{0}.{1}.plist", companyName, productName);
// Now construct the fully qualified path
string playerPrefsPath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library/Preferences"), plistFilename);
// Parse the player prefs file if it exists
if(File.Exists(playerPrefsPath))
{
// Parse the plist then cast it to a Dictionary
object plist = Plist.readPlist(playerPrefsPath);
Dictionary<string, object> parsed = plist as Dictionary<string, object>;
// Convert the dictionary data into an array of PlayerPrefPairs
PlayerPrefPair[] tempPlayerPrefs = new PlayerPrefPair[parsed.Count];
int i = 0;
foreach (KeyValuePair<string, object> pair in parsed)
{
if(pair.Value.GetType () == typeof(double))
{
// Some float values may come back as double, so convert them back to floats
tempPlayerPrefs[i] = new PlayerPrefPair() { Key = pair.Key, Value = (float)(double)pair.Value };
}
else
{
tempPlayerPrefs[i] = new PlayerPrefPair() { Key = pair.Key, Value = pair.Value };
}
i++;
}
// Return the results
return tempPlayerPrefs;
}
else
{
// No existing player prefs saved (which is valid), so just return an empty array
return new PlayerPrefPair[0];
}
}
else if(Application.platform == RuntimePlatform.WindowsEditor)
{
// From Unity docs: On Windows, PlayerPrefs are stored in the registry under HKCU\Software\[company name]\[product name] key, where company and product names are the names set up in Project Settings.
#if UNITY_5_5_OR_NEWER
// From Unity 5.5 editor player prefs moved to a specific location
Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Unity\\UnityEditor\\" + companyName + "\\" + productName);
#else
Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\" + companyName + "\\" + productName);
#endif
// Parse the registry if the specified registryKey exists
if (registryKey != null)
{
// Get an array of what keys (registry value names) are stored
string[] valueNames = registryKey.GetValueNames();
// Create the array of the right size to take the saved player prefs
PlayerPrefPair[] tempPlayerPrefs = new PlayerPrefPair[valueNames.Length];
// Parse and convert the registry saved player prefs into our array
int i = 0;
foreach (string valueName in valueNames)
{
string key = valueName;
// Remove the _h193410979 style suffix used on player pref keys in Windows registry
int index = key.LastIndexOf("_");
key = key.Remove(index, key.Length - index);
// Get the value from the registry
object ambiguousValue = registryKey.GetValue(valueName);
// Unfortunately floats will come back as an int (at least on 64 bit) because the float is stored as
// 64 bit but marked as 32 bit - which confuses the GetValue() method greatly!
if (ambiguousValue.GetType() == typeof(int))
{
// If the player pref is not actually an int then it must be a float, this will evaluate to true
// (impossible for it to be 0 and -1 at the same time)
if (PlayerPrefs.GetInt(key, -1) == -1 && PlayerPrefs.GetInt(key, 0) == 0)
{
// Fetch the float value from PlayerPrefs in memory
ambiguousValue = PlayerPrefs.GetFloat(key);
}
}
else if(ambiguousValue.GetType() == typeof(byte[]))
{
// On Unity 5 a string may be stored as binary, so convert it back to a string
ambiguousValue = System.Text.Encoding.Default.GetString((byte[])ambiguousValue);
}
// Assign the key and value into the respective record in our output array
tempPlayerPrefs[i] = new PlayerPrefPair() { Key = key, Value = ambiguousValue };
i++;
}
// Return the results
return tempPlayerPrefs;
}
else
{
// No existing player prefs saved (which is valid), so just return an empty array
return new PlayerPrefPair[0];
}
}
else
{
throw new NotSupportedException("PlayerPrefsEditor doesn't support this Unity Editor platform");
}
}
private void UpdateSearch()
{
// Clear any existing cached search results
filteredPlayerPrefs.Clear();
// Don't attempt to find the search results if a search filter hasn't actually been supplied
if (string.IsNullOrEmpty(searchFilter))
{
return;
}
int entryCount = deserializedPlayerPrefs.Count;
// Iterate through all the cached results and add any matches to filteredPlayerPrefs
for (int i = 0; i < entryCount; i++)
{
string fullKey = deserializedPlayerPrefs[i].Key;
string displayKey = fullKey;
// Special case for encrypted keys in auto decrypt mode, search should use decrypted values
bool isEncryptedPair = PlayerPrefsUtility.IsEncryptedKey(deserializedPlayerPrefs[i].Key);
if (automaticDecryption && isEncryptedPair)
{
displayKey = PlayerPrefsUtility.DecryptKey(fullKey);
}
// If the key contains the search filter (ToLower used on both parts to make this case insensitive)
if (displayKey.ToLower().Contains(searchFilter.ToLower()))
{
filteredPlayerPrefs.Add(deserializedPlayerPrefs[i]);
}
}
}
private void DrawSearchBar()
{
EditorGUILayout.BeginHorizontal();
// Heading
GUILayout.Label("Search", GUILayout.MaxWidth(50));
// Actual search box
string newSearchFilter = EditorGUILayout.TextField(searchFilter);
// If the requested search filter has changed
if (newSearchFilter != searchFilter)
{
searchFilter = newSearchFilter;
// Trigger UpdateSearch to calculate new search results
UpdateSearch();
}
EditorGUILayout.EndHorizontal();
}
private void DrawMainList()
{
// The bold table headings
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Key", EditorStyles.boldLabel);
GUILayout.Label("Value", EditorStyles.boldLabel);
GUILayout.Label("Type", EditorStyles.boldLabel, GUILayout.Width(37));
GUILayout.Label("Del", EditorStyles.boldLabel, GUILayout.Width(25));
EditorGUILayout.EndHorizontal();
// Create a GUIStyle that can be manipulated for the various text fields
GUIStyle textFieldStyle = new GUIStyle (GUI.skin.textField);
// Could be dealing with either the full list or search results, so get the right list
List<PlayerPrefPair> activePlayerPrefs = deserializedPlayerPrefs;
if (!string.IsNullOrEmpty(searchFilter))
{
activePlayerPrefs = filteredPlayerPrefs;
}
// Cache the entry count
int entryCount = activePlayerPrefs.Count;
// Record the last scroll position so we can calculate if the user has scrolled this frame
lastScrollPosition = scrollPosition;
// Start the scrollable area
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
// Ensure the scroll doesn't go below zero
if(scrollPosition.y < 0)
{
scrollPosition.y = 0;
}
// The following code has been optimised so that rather than attempting to draw UI for every single PlayerPref
// it instead only draws the UI for those currently visible in the scroll view and pads above and below those
// results to maintain the right size using GUILayout.Space(). This enables us to work with thousands of
// PlayerPrefs without slowing the interface to a halt.
// Fixed height of one of the rows in the table
float rowHeight = 18;
// Determine how many rows are visible on screen. For simplicity, use Screen.height (the overhead is negligible)
int visibleCount = Mathf.CeilToInt(Screen.height / rowHeight);
// Determine the index of the first player pref that should be drawn as visible in the scrollable area
int firstShownIndex = Mathf.FloorToInt(scrollPosition.y / rowHeight);
// Determine the bottom limit of the visible player prefs (last shown index + 1)
int shownIndexLimit = firstShownIndex + visibleCount;
// If the actual number of player prefs is smaller than the caculated limit, reduce the limit to match
if (entryCount < shownIndexLimit)
{
shownIndexLimit = entryCount;
}
// If the number of displayed player prefs is smaller than the number we can display (like we're at the end
// of the list) then move the starting index back to adjust
if (shownIndexLimit - firstShownIndex < visibleCount)
{
firstShownIndex -= visibleCount - (shownIndexLimit - firstShownIndex);
}
// Can't have a negative index of a first shown player pref, so clamp to 0
if (firstShownIndex < 0)
{
firstShownIndex = 0;
}
// Pad above the on screen results so that we're not wasting draw calls on invisible UI and the drawn player
// prefs end up in the same place in the list
GUILayout.Space(firstShownIndex * rowHeight);
// For each of the on screen results
for (int i = firstShownIndex; i < shownIndexLimit; i++)
{
// Detect if it's an encrypted player pref (these have key prefixes)
bool isEncryptedPair = PlayerPrefsUtility.IsEncryptedKey(activePlayerPrefs[i].Key);
// Colour code encrypted player prefs blue
if (isEncryptedPair)
{
if(UsingProSkin)
{
textFieldStyle.normal.textColor = new Color(0.5f,0.5f,1);
textFieldStyle.focused.textColor = new Color(0.5f,0.5f,1);
}
else
{
textFieldStyle.normal.textColor = new Color(0,0,1);
textFieldStyle.focused.textColor = new Color(0,0,1);
}
}
else
{
// Normal player prefs are just black
textFieldStyle.normal.textColor = GUI.skin.textField.normal.textColor;
textFieldStyle.focused.textColor = GUI.skin.textField.focused.textColor;
}
// The full key is the key that's actually stored in player prefs
string fullKey = activePlayerPrefs[i].Key;
// Display key is used so in the case of encrypted keys, we display the decrypted version instead (in
// auto-decrypt mode).
string displayKey = fullKey;
// Used for accessing the type information stored against the player pref
object deserializedValue = activePlayerPrefs[i].Value;
// Track whether the auto decrypt failed, so we can instead fallback to encrypted values and mark it red
bool failedAutoDecrypt = false;
// If this is an encrypted play pref and we're attempting to decrypt them, try to decrypt it!
if (isEncryptedPair && automaticDecryption)
{
// This may throw exceptions (e.g. if private key changes), so wrap in a try-catch
try
{
deserializedValue = PlayerPrefsUtility.GetEncryptedValue(fullKey, (string)deserializedValue);
displayKey = PlayerPrefsUtility.DecryptKey(fullKey);
}
catch
{
// Change the colour to red to highlight the decrypt failed
textFieldStyle.normal.textColor = Color.red;
textFieldStyle.focused.textColor = Color.red;
// Track that the auto decrypt failed, so we can prevent any editing
failedAutoDecrypt = true;
}
}
EditorGUILayout.BeginHorizontal();
// The type of player pref being stored (in auto decrypt mode this works with the decrypted values too)
Type valueType;
// If it's an encrypted playerpref, we're automatically decrypting and it didn't fail the earlier
// auto decrypt test
if (isEncryptedPair && automaticDecryption && !failedAutoDecrypt)
{
// Get the encrypted string
string encryptedValue = PlayerPrefs.GetString(fullKey);
// Set valueType appropiately based on which type identifier prefix the encrypted string starts with
if (encryptedValue.StartsWith(PlayerPrefsUtility.VALUE_FLOAT_PREFIX))
{
valueType = typeof(float);
}
else if (encryptedValue.StartsWith(PlayerPrefsUtility.VALUE_INT_PREFIX))
{
valueType = typeof(int);
}
else if (encryptedValue.StartsWith(PlayerPrefsUtility.VALUE_STRING_PREFIX) || string.IsNullOrEmpty (encryptedValue))
{
// Special case here, empty encrypted values will also report as strings
valueType = typeof(string);
}
else
{
throw new InvalidOperationException("Could not decrypt item, no match found in known encrypted key prefixes");
}
}
else
{
// Otherwise fallback to the type of the cached value (for non-encrypted values this will be
// correct). For encrypted values when not in auto-decrypt mode, this will return string type
valueType = deserializedValue.GetType();
}
// Display the PlayerPref key
EditorGUILayout.TextField(displayKey, textFieldStyle);
// Value display and user editing
// If we're dealing with a float
if (valueType == typeof(float))
{
float initialValue;
if (isEncryptedPair && automaticDecryption)
{
// Automatically decrypt the value if encrypted and in auto-decrypt mode
initialValue = PlayerPrefsUtility.GetEncryptedFloat(displayKey);
}
else
{
// Otherwise fetch the latest plain value from PlayerPrefs in memory
initialValue = PlayerPrefs.GetFloat(fullKey);
}
// Display the float editor field and get any changes in value
float newValue = EditorGUILayout.FloatField(initialValue, textFieldStyle);
// If the value has changed
if (newValue != initialValue)
{
// Store the changed value in player prefs, encrypting if necessary
if (isEncryptedPair)
{
string encryptedValue = PlayerPrefsUtility.VALUE_FLOAT_PREFIX + SimpleEncryption.EncryptFloat(newValue);
PlayerPrefs.SetString(fullKey, encryptedValue);
}
else
{
PlayerPrefs.SetFloat(fullKey, newValue);
}
// Save PlayerPrefs
PlayerPrefs.Save();
}
// Display the PlayerPref type
GUILayout.Label("float", GUILayout.Width(37));
}
else if (valueType == typeof(int)) // if we're dealing with an int
{
int initialValue;
if (isEncryptedPair && automaticDecryption)
{
// Automatically decrypt the value if encrypted and in auto-decrypt mode
initialValue = PlayerPrefsUtility.GetEncryptedInt(displayKey);
}
else
{
// Otherwise fetch the latest plain value from PlayerPrefs in memory
initialValue = PlayerPrefs.GetInt(fullKey);
}
// Display the int editor field and get any changes in value
int newValue = EditorGUILayout.IntField(initialValue, textFieldStyle);
// If the value has changed
if (newValue != initialValue)
{
// Store the changed value in player prefs, encrypting if necessary
if (isEncryptedPair)
{
string encryptedValue = PlayerPrefsUtility.VALUE_INT_PREFIX + SimpleEncryption.EncryptInt(newValue);
PlayerPrefs.SetString(fullKey, encryptedValue);
}
else
{
PlayerPrefs.SetInt(fullKey, newValue);
}
// Save PlayerPrefs
PlayerPrefs.Save();
}
// Display the PlayerPref type
GUILayout.Label("int", GUILayout.Width(37));
}
else if (valueType == typeof(string)) // if we're dealing with a string
{
string initialValue;
if (isEncryptedPair && automaticDecryption && !failedAutoDecrypt)
{
// Automatically decrypt the value if encrypted and in auto-decrypt mode
initialValue = PlayerPrefsUtility.GetEncryptedString(displayKey);
}
else
{
// Otherwise fetch the latest plain value from PlayerPrefs in memory
initialValue = PlayerPrefs.GetString(fullKey);
}
// Display the text (string) editor field and get any changes in value
string newValue = EditorGUILayout.TextField(initialValue, textFieldStyle);
// If the value has changed
if (newValue != initialValue && !failedAutoDecrypt)
{
// Store the changed value in player prefs, encrypting if necessary
if (isEncryptedPair)
{
string encryptedValue = PlayerPrefsUtility.VALUE_STRING_PREFIX + SimpleEncryption.EncryptString(newValue);
PlayerPrefs.SetString(fullKey, encryptedValue);
}
else
{
PlayerPrefs.SetString(fullKey, newValue);
}
// Save PlayerPrefs
PlayerPrefs.Save();
}
if (isEncryptedPair && !automaticDecryption && !string.IsNullOrEmpty(initialValue))
{
// Because encrypted values when not in auto-decrypt mode are stored as string, determine their
// encrypted type and display that instead for these encrypted PlayerPrefs
PlayerPrefType playerPrefType = (PlayerPrefType)(int)char.GetNumericValue(initialValue[0]);
GUILayout.Label(playerPrefType.ToString().ToLower(), GUILayout.Width(37));
}
else
{
// Display the PlayerPref type
GUILayout.Label("string", GUILayout.Width(37));
}
}
// Delete button
if (GUILayout.Button("X", GUILayout.Width(25)))
{
// Delete the key from player prefs
PlayerPrefs.DeleteKey(fullKey);
// Tell Unity to Save PlayerPrefs
PlayerPrefs.Save();
// Delete the cached record so the list updates immediately
DeleteCachedRecord(fullKey);
}
EditorGUILayout.EndHorizontal();
}
// Calculate the padding at the bottom of the scroll view (because only visible player pref rows are drawn)
float bottomPadding = (entryCount - shownIndexLimit) * rowHeight;
// If the padding is positive, pad the bottom so that the layout and scroll view size is correct still
if (bottomPadding > 0)
{
GUILayout.Space(bottomPadding);
}
EditorGUILayout.EndScrollView();
// Display the number of player prefs
GUILayout.Label("Entry Count: " + entryCount);
}
private void DrawAddEntry()
{
// Create a GUIStyle that can be manipulated for the various text fields
GUIStyle textFieldStyle = new GUIStyle (GUI.skin.textField);
// Create a space
EditorGUILayout.Space();
// Heading
GUILayout.Label("Add Player Pref", EditorStyles.boldLabel);
// UI for whether the new player pref is encrypted and what type it is
EditorGUILayout.BeginHorizontal();
newEntryIsEncrypted = GUILayout.Toggle(newEntryIsEncrypted, "Encrypt");
newEntryType = (PlayerPrefType)GUILayout.SelectionGrid((int)newEntryType, new string[] { "float", "int", "string" }, 3);
EditorGUILayout.EndHorizontal();
// Key and Value headings
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Key", EditorStyles.boldLabel);
GUILayout.Label("Value", EditorStyles.boldLabel);
EditorGUILayout.EndHorizontal();
// If the new value will be encrypted tint the text boxes blue (in line with the display style for existing
// encrypted player prefs)
if (newEntryIsEncrypted)
{
if(UsingProSkin)
{
textFieldStyle.normal.textColor = new Color(0.5f,0.5f,1);
textFieldStyle.focused.textColor = new Color(0.5f,0.5f,1);
}
else
{
textFieldStyle.normal.textColor = new Color(0,0,1);
textFieldStyle.focused.textColor = new Color(0,0,1);
}
}
EditorGUILayout.BeginHorizontal();
// Track the next control so we can detect key events in it
GUI.SetNextControlName("newEntryKey");
// UI for the new key text box
newEntryKey = EditorGUILayout.TextField(newEntryKey, textFieldStyle);
// Track the next control so we can detect key events in it
GUI.SetNextControlName("newEntryValue");
// Display the correct UI field editor based on what type of player pref is being created
if (newEntryType == PlayerPrefType.Float)
{
newEntryValueFloat = EditorGUILayout.FloatField(newEntryValueFloat, textFieldStyle);
}
else if (newEntryType == PlayerPrefType.Int)
{
newEntryValueInt = EditorGUILayout.IntField(newEntryValueInt, textFieldStyle);
}
else
{
newEntryValueString = EditorGUILayout.TextField(newEntryValueString, textFieldStyle);
}
// If the user hit enter while either the key or value fields were being edited
bool keyboardAddPressed = Event.current.isKey && Event.current.keyCode == KeyCode.Return && Event.current.type == EventType.KeyUp && (GUI.GetNameOfFocusedControl() == "newEntryKey" || GUI.GetNameOfFocusedControl() == "newEntryValue");
// If the user clicks the Add button or hits return (and there is a non-empty key), create the player pref
if ((GUILayout.Button("Add", GUILayout.Width(40)) || keyboardAddPressed) && !string.IsNullOrEmpty(newEntryKey))
{
// If the player pref we're creating is encrypted
if (newEntryIsEncrypted)
{
// Encrypt the key
string encryptedKey = PlayerPrefsUtility.KEY_PREFIX + SimpleEncryption.EncryptString(newEntryKey);
// Note: All encrypted values are stored as string
string encryptedValue;
// Calculate the encrypted value
if (newEntryType == PlayerPrefType.Float)
{
encryptedValue = PlayerPrefsUtility.VALUE_FLOAT_PREFIX + SimpleEncryption.EncryptFloat(newEntryValueFloat);
}
else if (newEntryType == PlayerPrefType.Int)
{
encryptedValue = PlayerPrefsUtility.VALUE_INT_PREFIX + SimpleEncryption.EncryptInt(newEntryValueInt);
}
else
{
encryptedValue = PlayerPrefsUtility.VALUE_STRING_PREFIX + SimpleEncryption.EncryptString(newEntryValueString);
}
// Record the new player pref in PlayerPrefs
PlayerPrefs.SetString(encryptedKey, encryptedValue);
// Cache the addition
CacheRecord(encryptedKey, encryptedValue);
}
else
{
if (newEntryType == PlayerPrefType.Float)
{
// Record the new player pref in PlayerPrefs
PlayerPrefs.SetFloat(newEntryKey, newEntryValueFloat);
// Cache the addition
CacheRecord(newEntryKey, newEntryValueFloat);
}
else if (newEntryType == PlayerPrefType.Int)
{
// Record the new player pref in PlayerPrefs
PlayerPrefs.SetInt(newEntryKey, newEntryValueInt);
// Cache the addition
CacheRecord(newEntryKey, newEntryValueInt);
}
else
{
// Record the new player pref in PlayerPrefs
PlayerPrefs.SetString(newEntryKey, newEntryValueString);
// Cache the addition
CacheRecord(newEntryKey, newEntryValueString);
}
}
// Tell Unity to save the PlayerPrefs
PlayerPrefs.Save();
// Force a repaint since hitting the return key won't invalidate layout on its own
Repaint();
// Reset the values
newEntryKey = "";
newEntryValueFloat = 0;
newEntryValueInt = 0;
newEntryValueString = "";
// Deselect
GUI.FocusControl("");
}
EditorGUILayout.EndHorizontal();
}
private void DrawBottomMenu()
{
EditorGUILayout.Space();
// Allow the user to import player prefs from another project (helpful when renaming product name)
GUILayout.Label("Import", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
GUIStyle textFieldStyle = new GUIStyle(GUI.skin.textField);
if (newEntryIsEncrypted)
{
if (UsingProSkin)
{
textFieldStyle.normal.textColor = new Color(0.5f, 0.5f, 1);
textFieldStyle.focused.textColor = new Color(0.5f, 0.5f, 1);
}
else
{
textFieldStyle.normal.textColor = new Color(0, 0, 1);
textFieldStyle.focused.textColor = new Color(0, 0, 1);
}
}
importCompanyName = EditorGUILayout.TextField(importCompanyName, textFieldStyle);
importProductName = EditorGUILayout.TextField(importProductName, textFieldStyle);
if(GUILayout.Button("Import"))
{
// Walk through all the imported player prefs and apply them to the current player prefs
PlayerPrefPair[] importedPairs = RetrieveSavedPrefs(importCompanyName, importProductName);
for (int i = 0; i < importedPairs.Length; i++)
{
Type type = importedPairs[i].Value.GetType();
if (type == typeof(float))
PlayerPrefs.SetFloat(importedPairs[i].Key, (float)importedPairs[i].Value);
else if (type == typeof(int))
PlayerPrefs.SetInt(importedPairs[i].Key, (int)importedPairs[i].Value);
else if (type == typeof(string))
PlayerPrefs.SetString(importedPairs[i].Key, (string)importedPairs[i].Value);
// Cache any new records until they are reimported from disk
CacheRecord(importedPairs[i].Key, importedPairs[i].Value);
}
// Force a save
PlayerPrefs.Save();
}
EditorGUILayout.EndHorizontal();
// UI for toggling automatic decryption on and off
automaticDecryption = EditorGUILayout.Toggle("Auto-Decryption", automaticDecryption);
EditorGUILayout.BeginHorizontal();
// Delete all player prefs
if (GUILayout.Button("Delete All Preferences"))
{
PlayerPrefs.DeleteAll();
PlayerPrefs.Save();
// Clear the cache too, for an instant visibility update for OSX
deserializedPlayerPrefs.Clear();
}
GUILayout.Space(15);
// Mainly needed for OSX, this will encourage PlayerPrefs to save to file (but still may take a few seconds)
if (GUILayout.Button("Force Save"))
{
PlayerPrefs.Save();
}
EditorGUILayout.EndHorizontal();
}
private void OnGUI()
{
EditorGUILayout.Space();
if(Application.platform == RuntimePlatform.OSXEditor)
{
// Construct the plist filename from the project's settings
string plistFilename = string.Format("unity.{0}.{1}.plist", PlayerSettings.companyName, PlayerSettings.productName);
// Now construct the fully qualified path
string playerPrefsPath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library/Preferences"), plistFilename);
// Determine when the plist was last written to
DateTime lastWriteTime = File.GetLastWriteTimeUtc(playerPrefsPath);
// If we haven't deserialized the player prefs already, or the written file has changed then deserialize
// the latest version
if(!lastDeserialization.HasValue || lastDeserialization.Value != lastWriteTime)
{
// Deserialize the actual player prefs from file into a cache
deserializedPlayerPrefs = new List<PlayerPrefPair>(RetrieveSavedPrefs(PlayerSettings.companyName, PlayerSettings.productName));
// Record the version of the file we just read, so we know if it changes in the future
lastDeserialization = lastWriteTime;
}
if(lastWriteTime != MISSING_DATETIME)
{
GUILayout.Label("PList Last Written: " + lastWriteTime.ToString());
}
else
{
GUILayout.Label("PList Does Not Exist");
}
}
else if(Application.platform == RuntimePlatform.WindowsEditor)
{
// Windows works a bit differently to OSX, we just regularly query the registry. So don't query too often
if (!lastDeserialization.HasValue || DateTime.UtcNow - lastDeserialization.Value > TimeSpan.FromMilliseconds(500))
{
// Deserialize the actual player prefs from registry into a cache
deserializedPlayerPrefs = new List<PlayerPrefPair>(RetrieveSavedPrefs(PlayerSettings.companyName, PlayerSettings.productName));
// Record the latest time, so we don't fetch again too quickly
lastDeserialization = DateTime.UtcNow;
}
}
DrawSearchBar();
DrawMainList();
DrawAddEntry();
DrawBottomMenu();
// If the user has scrolled, deselect - this is because control IDs within carousel will change when scrolled
// so we'd end up with the wrong box selected.
if (scrollPosition != lastScrollPosition)
{
// Deselect
GUI.FocusControl("");
}
}
private void CacheRecord(string key, object value)
{
// First of all check if this key already exists, if so replace it's value with the new value
bool replaced = false;
int entryCount = deserializedPlayerPrefs.Count;
for (int i = 0; i < entryCount; i++)
{
// Found the key - it exists already
if (deserializedPlayerPrefs[i].Key == key)
{
// Update the cached pref with the new value
deserializedPlayerPrefs[i] = new PlayerPrefPair() { Key = key, Value = value };
// Mark the replacement so we no longer need to add it
replaced = true;
break;
}
}
// Player pref doesn't already exist (and wasn't replaced) so add it as new
if(!replaced)
{
// Cache a player pref the user just created so it can be instantly display (mainly for OSX)
deserializedPlayerPrefs.Add(new PlayerPrefPair() { Key = key, Value = value });
}
// Update the search if it's active
UpdateSearch();
}
private void DeleteCachedRecord(string fullKey)
{
keyQueuedForDeletion = fullKey;
}
// OnInspectorUpdate() is called by Unity at 10 times a second
private void OnInspectorUpdate()
{
// If a player pref has been specified for deletion
if(!string.IsNullOrEmpty(keyQueuedForDeletion))
{
// If the user just deleted a player pref, find the ID and defer it for deletion by OnInspectorUpdate()
if (deserializedPlayerPrefs != null)
{
int entryCount = deserializedPlayerPrefs.Count;
for (int i = 0; i < entryCount; i++)
{
if (deserializedPlayerPrefs[i].Key == keyQueuedForDeletion)
{
deserializedPlayerPrefs.RemoveAt(i);
break;
}
}
}
// Remove the queued key since we've just deleted it
keyQueuedForDeletion = null;
// Update the search results and repaint the window
UpdateSearch();
Repaint();
}
else if (inspectorUpdateFrame % 10 == 0) // Once a second (every 10th frame)
{
// Force the window to repaint
Repaint();
}
// Track what frame we're on, so we can call code less often
inspectorUpdateFrame++;
}
bool UsingProSkin
{
get
{
#if UNITY_3_4
if(EditorPrefs.GetInt("UserSkin") == 1)
{
return true;
}
else
{
return false;
}
#else
return EditorGUIUtility.isProSkin;
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 861677afe8cab4d3299ebf446fd71db0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,347 @@
using System;
using System.Globalization;
using UnityEngine;
using Sabresaurus.PlayerPrefsExtensions;
public static class PlayerPrefsUtility
{
// Each encrypted player pref key is given a prefix (this helps the Editor to identify them)
public const string KEY_PREFIX = "ENC-";
// Each encrypted value is prefixed with a type identifier (because encrypted values are stored as strings)
public const string VALUE_FLOAT_PREFIX = "0";
public const string VALUE_INT_PREFIX = "1";
public const string VALUE_STRING_PREFIX = "2";
/// <summary>
/// Determines if the specified player pref key refers to an encrypted record
/// </summary>
public static bool IsEncryptedKey (string key)
{
// Encrypted keys use a special prefix
if(key.StartsWith(KEY_PREFIX))
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Decrypts the specified key
/// </summary>
public static string DecryptKey(string encryptedKey)
{
if(encryptedKey.StartsWith(KEY_PREFIX))
{
// Remove the key prefix from the encrypted key
string strippedKey = encryptedKey.Substring(KEY_PREFIX.Length);
// Return the decrypted key
return SimpleEncryption.DecryptString(strippedKey);
}
else
{
throw new InvalidOperationException("Could not decrypt item, no match found in known encrypted key prefixes");
}
}
/// <summary>
/// Encrypted version of PlayerPrefs.SetFloat(), stored key and value is encrypted in player prefs
/// </summary>
public static void SetEncryptedFloat(string key, float value)
{
string encryptedKey = SimpleEncryption.EncryptString(key);
string encryptedValue = SimpleEncryption.EncryptFloat(value);
// Store the encrypted key and value (with relevant identifying prefixes) in PlayerPrefs
PlayerPrefs.SetString(KEY_PREFIX + encryptedKey, VALUE_FLOAT_PREFIX + encryptedValue);
}
/// <summary>
/// Encrypted version of PlayerPrefs.SetInt(), stored key and value is encrypted in player prefs
/// </summary>
public static void SetEncryptedInt(string key, int value)
{
string encryptedKey = SimpleEncryption.EncryptString(key);
string encryptedValue = SimpleEncryption.EncryptInt(value);
// Store the encrypted key and value (with relevant identifying prefixes) in PlayerPrefs
PlayerPrefs.SetString(KEY_PREFIX + encryptedKey, VALUE_INT_PREFIX + encryptedValue);
}
/// <summary>
/// Encrypted version of PlayerPrefs.SetString(), stored key and value is encrypted in player prefs
/// </summary>
public static void SetEncryptedString(string key, string value)
{
string encryptedKey = SimpleEncryption.EncryptString(key);
string encryptedValue = SimpleEncryption.EncryptString(value);
// Store the encrypted key and value (with relevant identifying prefixes) in PlayerPrefs
PlayerPrefs.SetString(KEY_PREFIX + encryptedKey, VALUE_STRING_PREFIX + encryptedValue);
}
/// <summary>
/// Helper method that can handle any of the encrypted player pref types, returning a float, int or string based
/// on what type of value has been stored.
/// </summary>
public static object GetEncryptedValue(string encryptedKey, string encryptedValue)
{
// See what type identifier the encrypted value starts
if(encryptedValue.StartsWith(VALUE_FLOAT_PREFIX))
{
// It's a float, so decrypt it as a float and return the value
return GetEncryptedFloat(SimpleEncryption.DecryptString(encryptedKey.Substring(KEY_PREFIX.Length)));
}
else if(encryptedValue.StartsWith(VALUE_INT_PREFIX))
{
// It's an int, so decrypt it as an int and return the value
return GetEncryptedInt(SimpleEncryption.DecryptString(encryptedKey.Substring(KEY_PREFIX.Length)));
}
else if(encryptedValue.StartsWith(VALUE_STRING_PREFIX))
{
// It's a string, so decrypt it as a string and return the value
return GetEncryptedString(SimpleEncryption.DecryptString(encryptedKey.Substring(KEY_PREFIX.Length)));
}
else
{
throw new InvalidOperationException("Could not decrypt item, no match found in known encrypted key prefixes");
}
}
/// <summary>
/// Encrypted version of PlayerPrefs.GetFloat(), an unencrypted key is passed and the value is returned decrypted
/// </summary>
public static float GetEncryptedFloat(string key, float defaultValue = 0.0f)
{
// Encrypt and prefix the key so we can look it up from player prefs
string encryptedKey = KEY_PREFIX + SimpleEncryption.EncryptString(key);
// Look up the encrypted value
string fetchedString = PlayerPrefs.GetString(encryptedKey);
if(!string.IsNullOrEmpty(fetchedString))
{
// Strip out the type identifier character
fetchedString = fetchedString.Remove(0, 1);
// Decrypt and return the float value
return SimpleEncryption.DecryptFloat (fetchedString);
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
/// <summary>
/// Encrypted version of PlayerPrefs.GetInt(), an unencrypted key is passed and the value is returned decrypted
/// </summary>
public static int GetEncryptedInt(string key, int defaultValue = 0)
{
// Encrypt and prefix the key so we can look it up from player prefs
string encryptedKey = KEY_PREFIX + SimpleEncryption.EncryptString(key);
// Look up the encrypted value
string fetchedString = PlayerPrefs.GetString(encryptedKey);
if(!string.IsNullOrEmpty(fetchedString))
{
// Strip out the type identifier character
fetchedString = fetchedString.Remove(0, 1);
// Decrypt and return the int value
return SimpleEncryption.DecryptInt (fetchedString);
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
/// <summary>
/// Encrypted version of PlayerPrefs.GetString(), an unencrypted key is passed and the value is returned decrypted
/// </summary>
public static string GetEncryptedString(string key, string defaultValue = "")
{
// Encrypt and prefix the key so we can look it up from player prefs
string encryptedKey = KEY_PREFIX + SimpleEncryption.EncryptString(key);
// Look up the encrypted value
string fetchedString = PlayerPrefs.GetString(encryptedKey);
if(!string.IsNullOrEmpty(fetchedString))
{
// Strip out the type identifier character
fetchedString = fetchedString.Remove(0, 1);
// Decrypt and return the string value
return SimpleEncryption.DecryptString (fetchedString);
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
/// <summary>
/// Helper method to store a bool in PlayerPrefs (stored as an int)
/// </summary>
public static void SetBool(string key, bool value)
{
// Store the bool as an int (1 for true, 0 for false)
if(value)
{
PlayerPrefs.SetInt(key, 1);
}
else
{
PlayerPrefs.SetInt(key, 0);
}
}
/// <summary>
/// Helper method to retrieve a bool from PlayerPrefs (stored as an int)
/// </summary>
public static bool GetBool(string key, bool defaultValue = false)
{
// Use HasKey to check if the bool has been stored (as int defaults to 0 which is ambiguous with a stored False)
if(PlayerPrefs.HasKey(key))
{
int value = PlayerPrefs.GetInt(key);
// As in C, assume zero is false and any non-zero value (including its intended 1) is true
if(value != 0)
{
return true;
}
else
{
return false;
}
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
/// <summary>
/// Helper method to store an enum value in PlayerPrefs (stored using the string name of the enum)
/// </summary>
public static void SetEnum(string key, Enum value)
{
// Convert the enum value to its string name (as opposed to integer index) and store it in a string PlayerPref
PlayerPrefs.SetString(key, value.ToString());
}
/// <summary>
/// Generic helper method to retrieve an enum value from PlayerPrefs and parse it from its stored string into the
/// specified generic type. This method should generally be preferred over the non-generic equivalent
/// </summary>
public static T GetEnum<T>(string key, T defaultValue = default(T)) where T: struct
{
// Fetch the string value from PlayerPrefs
string stringValue = PlayerPrefs.GetString (key);
if(!string.IsNullOrEmpty(stringValue))
{
// Existing value, so parse it using the supplied generic type and cast before returning it
return (T)Enum.Parse(typeof(T), stringValue);
}
else
{
// No player pref for this, just return default. If no default is supplied this will be the enum's default
return defaultValue;
}
}
/// <summary>
/// Non-generic helper method to retrieve an enum value from PlayerPrefs (stored as a string). Default value must be
/// passed, passing null will mean you need to do a null check where you call this method. Generally try to use the
/// generic version of this method instead: GetEnum<T>
/// </summary>
public static object GetEnum(string key, Type enumType, object defaultValue)
{
// Fetch the string value from PlayerPrefs
string value = PlayerPrefs.GetString (key);
if(!string.IsNullOrEmpty(value))
{
// Existing value, parse it using the supplied type, then return the result as an object
return Enum.Parse(enumType, value);
}
else
{
// No player pref for this key, so just return supplied default. It's required to supply a default value,
// you can just pass null, but you would then need to do a null check where you call non-generic GetEnum().
// Consider using GetEnum<T>() which doesn't require a default to be passed (supplying default(T) instead)
return defaultValue;
}
}
/// <summary>
/// Helper method to store a DateTime (complete with its timezone) in PlayerPrefs as a string
/// </summary>
public static void SetDateTime(string key, DateTime value)
{
// Convert to an ISO 8601 compliant string ("o"), so that it's fully qualified, then store in PlayerPrefs
PlayerPrefs.SetString(key, value.ToString("o", CultureInfo.InvariantCulture));
}
/// <summary>
/// Helper method to retrieve a DateTime from PlayerPrefs (stored as a string) and return a DateTime complete with
/// timezone (works with UTC and local DateTimes)
/// </summary>
public static DateTime GetDateTime(string key, DateTime defaultValue = new DateTime())
{
// Fetch the string value from PlayerPrefs
string stringValue = PlayerPrefs.GetString(key);
if(!string.IsNullOrEmpty(stringValue))
{
// Make sure to parse it using Roundtrip Kind otherwise a local time would come out as UTC
return DateTime.Parse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
/// <summary>
/// Helper method to store a TimeSpan in PlayerPrefs as a string
/// </summary>
public static void SetTimeSpan(string key, TimeSpan value)
{
// Use the TimeSpan's ToString() method to encode it as a string which is then stored in PlayerPrefs
PlayerPrefs.SetString(key, value.ToString());
}
/// <summary>
/// Helper method to retrieve a TimeSpan from PlayerPrefs (stored as a string)
/// </summary>
public static TimeSpan GetTimeSpan(string key, TimeSpan defaultValue = new TimeSpan())
{
// Fetch the string value from PlayerPrefs
string stringValue = PlayerPrefs.GetString(key);
if(!string.IsNullOrEmpty(stringValue))
{
// Parse the string and return the TimeSpan
return TimeSpan.Parse(stringValue);
}
else
{
// No existing player pref value, so return defaultValue instead
return defaultValue;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cf5395079e6249f1b6c0ba93c4b41ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
PlayerPrefs Editor & Utilities, Version 1.1.0
Sabresaurus Ltd.
web: http://www.sabresaurus.com
contact: http://www.sabresaurus.com/contact
For a more comprehensive quick start guide and API documentation please go to http://sabresaurus.com/docs/playerprefs-editor-utilities-documentation/
== PlayerPrefs Editor ==
To open the PlayersPrefs Editor go to Window -> Player Prefs Editor
This will open an editor window which you can dock like any other Unity window.
= The Player Prefs List =
If you have existing saved player prefs you should see them listed in the main window. You can change the values just by changing the value text box, you can also delete one of these existing player prefs by clicking the X button on the right.
= Search =
The editor supports filtering keys by enterring a keyword in the search textbox at the top. As you type the search results will refine. Search is case-insensitive and if auto-decrypt is turned on it will also work with encrypted player prefs.
= Adding A New Player Pref =
At the bottom of the editor you'll see a section for adding a new player pref. There are toggle options to determine what type it is and a checkbox for whether the player pref should be encrypted. Once you've selected the right settings and filled in a key and value hit the Add button to instantly add the player pref.
= Deleting An Existing Player Pref =
To delete an existing player pref, click the X button next to the player pref in the list. You can also delete all player prefs by clicking the Delete All button at the bottom of the editor.
== PlayerPrefs Utilities ==
IMPORTANT: If using encryption, make sure you change the key specified in SimpleEncryption.cs, this will make sure your key is unique and make the protection stronger.
In PlayerPrefsUtility.cs you'll find a set of utility methods for dealing with encryption and also new data types. There is documentation within this file explaining how each method works. There is also additional documentation at http://sabresaurus.com/docs/playerprefs-editor-utilities-documentation/

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ca2d7a157f23443cfa43e509ad236bb7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
namespace Sabresaurus.PlayerPrefsExtensions
{
public static class SimpleEncryption
{
// IMPORTANT: Make sure to change this key for each project you use this encryption in to help secure your
// encrypted values. This key must be exactly 32 characters long (256 bit).
private static string key = ":{j%6j?E:t#}G10mM%9hp5S=%}2,Y26C";
// Cache the encryption provider
private static RijndaelManaged provider = null;
private static void SetupProvider()
{
// Create a new encryption provider
provider = new RijndaelManaged();
// Get the bytes from the supplied string key and use it as the provider's key
provider.Key = Encoding.ASCII.GetBytes(key);
// Ensure that the same data is always encrypted the same way when used with the same key
provider.Mode = CipherMode.ECB;
}
/// <summary>
/// Encrypts the specified string using the key stored in SimpleEncryption and returns the encrypted result
/// </summary>
public static string EncryptString(string sourceString)
{
if(provider == null)
{
// Encryption provider hasn't been set up yet, so set it up
SetupProvider();
}
// Create an encryptor to encrypt the bytes
ICryptoTransform encryptor = provider.CreateEncryptor();
// Convert the source string into bytes to be encrypted
byte[] sourceBytes = Encoding.UTF8.GetBytes(sourceString);
// Encrypt the bytes using the encryptor we just created
byte[] outputBytes = encryptor.TransformFinalBlock(sourceBytes, 0, sourceBytes.Length);
// Convert the encrypted bytes into a Base 64 string, so we can safely represent them as a string and return
// that string
return Convert.ToBase64String(outputBytes);
}
/// <summary>
/// Decrypts the specified string from its specified encrypted value into the returned decrypted value using the
/// key stored in SimpleEncryption
/// </summary>
public static string DecryptString(string sourceString)
{
if(provider == null)
{
// Encryption provider hasn't been set up yet, so set it up
SetupProvider();
}
// Create a decryptor to decrypt the encrypted bytes
ICryptoTransform decryptor = provider.CreateDecryptor();
// Convert the base 64 string representing the encrypted bytes back into an array of encrypted bytes
byte[] sourceBytes = Convert.FromBase64String(sourceString);
// Use the decryptor we just created to decrypt those bytes
byte[] outputBytes = decryptor.TransformFinalBlock(sourceBytes, 0, sourceBytes.Length);
// Turn the decrypted bytes back into the decrypted string and return it
return Encoding.UTF8.GetString(outputBytes);
}
/// <summary>
/// Encrypts the specified float value and returns an encrypted string
/// </summary>
public static string EncryptFloat(float value)
{
// Convert the float into its 4 bytes
byte[] bytes = BitConverter.GetBytes(value);
// Represent those bytes as a base 64 string
string base64 = Convert.ToBase64String(bytes);
// Return the encrypted version of that base 64 string
return SimpleEncryption.EncryptString(base64);
}
/// <summary>
/// Encrypts the specified int value and returns an encrypted string
/// </summary>
public static string EncryptInt(int value)
{
// Convert the int value into its 4 bytes
byte[] bytes = BitConverter.GetBytes(value);
// Represent those bytes as a base 64 string
string base64 = Convert.ToBase64String(bytes);
// Return the encrypted version of that base 64 string
return SimpleEncryption.EncryptString(base64);
}
/// <summary>
/// Decrypts the encrypted string representing a float into the decrypted float
/// </summary>
public static float DecryptFloat(string sourceString)
{
// Decrypt the encrypted string
string decryptedString = SimpleEncryption.DecryptString(sourceString);
// Convert the decrypted Base 64 representation back into bytes
byte[] bytes = Convert.FromBase64String(decryptedString);
// Turn the bytes back into a float and return it
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Decrypts the encrypted string representing an int into the decrypted int
/// </summary>
public static int DecryptInt(string sourceString)
{
// Decrypt the encrypted string
string decryptedString = SimpleEncryption.DecryptString(sourceString);
// Convert the decrypted Base 64 representation back into bytes
byte[] bytes = Convert.FromBase64String(decryptedString);
// Turn the bytes back into a int and return it
return BitConverter.ToInt32(bytes, 0);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d971fdbb122d948b3a6d6ca2b852eeaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -55,5 +55,5 @@ SphereCollider:
m_IsTrigger: 0 m_IsTrigger: 0
m_Enabled: 1 m_Enabled: 1
serializedVersion: 2 serializedVersion: 2
m_Radius: 3 m_Radius: 4
m_Center: {x: 0, y: 0, z: 0} m_Center: {x: 0, y: 0, z: 0}

View File

@@ -9,6 +9,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 2175438886443421650} - component: {fileID: 2175438886443421650}
- component: {fileID: 6147191614421259820}
m_Layer: 0 m_Layer: 0
m_Name: OrangeDuckCollectible m_Name: OrangeDuckCollectible
m_TagString: Untagged m_TagString: Untagged
@@ -32,6 +33,18 @@ Transform:
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 0 m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &6147191614421259820
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4296518137834665065}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9f8931783fc280145becdca83483472b, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1001 &3397460197389077781 --- !u!1001 &3397460197389077781
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -0,0 +1,485 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3288657274700709570
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3288657274700709571}
- component: {fileID: 3288657274700709581}
- component: {fileID: 3288657274700709580}
- component: {fileID: 2173880722742740201}
m_Layer: 5
m_Name: StickerDisplay
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3288657274700709571
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657274700709570}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 3288657276581250048}
- {fileID: 3288657276576761438}
- {fileID: 3288657275205251324}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3288657274700709581
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657274700709570}
m_CullTransparentMesh: 0
--- !u!114 &3288657274700709580
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657274700709570}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 21300000, guid: 3c429106c1b77d443a22ca9574cff669, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &2173880722742740201
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657274700709570}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3880ade5e4f047a4abdaa396235ba1cb, type: 3}
m_Name:
m_EditorClassIdentifier:
label: {fileID: 3288657275205251325}
duckCount: {fileID: 3288657276576761439}
stickerImage: {fileID: 3288657276581250049}
--- !u!1 &3288657275205251315
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3288657275205251324}
- component: {fileID: 3288657275205251326}
- component: {fileID: 3288657275205251325}
m_Layer: 5
m_Name: Sticker Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3288657275205251324
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657275205251315}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 3288657274700709571}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 45.6}
m_SizeDelta: {x: 0, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3288657275205251326
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657275205251315}
m_CullTransparentMesh: 0
--- !u!114 &3288657275205251325
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657275205251315}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_text: De koning
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4278190080
m_fontColor: {r: 0, g: 0, b: 0, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_outlineColor:
serializedVersion: 2
rgba: 4278190080
m_fontSize: 40
m_fontSizeBase: 40
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_textAlignment: 258
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_firstOverflowCharacterIndex: -1
m_linkedTextComponent: {fileID: 0}
m_isLinkedTextComponent: 0
m_isTextTruncated: 0
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_ignoreRectMaskCulling: 0
m_ignoreCulling: 1
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_VertexBufferAutoSizeReduction: 1
m_firstVisibleCharacter: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_textInfo:
textComponent: {fileID: 3288657275205251325}
characterCount: 9
spriteCount: 0
spaceCount: 1
wordCount: 2
linkCount: 0
lineCount: 1
pageCount: 1
materialCount: 1
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_spriteAnimator: {fileID: 0}
m_hasFontAssetChanged: 0
m_subTextObjects:
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &3288657276576761437
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3288657276576761438}
- component: {fileID: 3288657276576761432}
- component: {fileID: 3288657276576761439}
m_Layer: 5
m_Name: Sticker Count
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3288657276576761438
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276576761437}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 3288657274700709571}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -115, y: -40}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3288657276576761432
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276576761437}
m_CullTransparentMesh: 0
--- !u!114 &3288657276576761439
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276576761437}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_text: 100x
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4284177243
m_fontColor: {r: 0.3584906, g: 0.3584906, b: 0.3584906, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_outlineColor:
serializedVersion: 2
rgba: 4278190080
m_fontSize: 40
m_fontSizeBase: 40
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_textAlignment: 260
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_firstOverflowCharacterIndex: -1
m_linkedTextComponent: {fileID: 0}
m_isLinkedTextComponent: 0
m_isTextTruncated: 0
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_ignoreRectMaskCulling: 0
m_ignoreCulling: 1
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_VertexBufferAutoSizeReduction: 1
m_firstVisibleCharacter: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_textInfo:
textComponent: {fileID: 3288657276576761439}
characterCount: 4
spriteCount: 0
spaceCount: 0
wordCount: 1
linkCount: 0
lineCount: 1
pageCount: 1
materialCount: 1
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_spriteAnimator: {fileID: 0}
m_hasFontAssetChanged: 0
m_subTextObjects:
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &3288657276581250055
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3288657276581250048}
- component: {fileID: 3288657276581250050}
- component: {fileID: 3288657276581250049}
m_Layer: 5
m_Name: Sticker Image
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3288657276581250048
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276581250055}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 3288657274700709571}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -134.3}
m_SizeDelta: {x: -20, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3288657276581250050
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276581250055}
m_CullTransparentMesh: 0
--- !u!114 &3288657276581250049
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3288657276581250055}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 21300000, guid: 869d7ef4bd2317d4e974b6a69247e9bb, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 595ddc8575349094bac51c919df6a7cf
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Resources.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3e2c3b6c2d0c98e40aa8b2292427293e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1b774875b31a904d94d52329cf4b070
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
{"AccessToken":"pk.eyJ1IjoiaW1tb3J0YWx5MDA3IiwiYSI6ImNqemdxYW03dTBvaWgzZG81YzR0NzRnb3kifQ.Q4pTWDwkwOAsAtM56VBZww","MemoryCacheSize":500,"FileCacheSize":25000,"DefaultTimeout":30,"AutoRefreshCache":false}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d9e7bd39391dd7d409f8ce0f72b7cdc5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

8
Assets/Scripts.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 736309984b3e50248a58b7c37507bb9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 689794eb882594347a2210bf619fac6c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
// Just add this script to your camera. It doesn't need any configuration.
using UnityEngine;
public class TouchCamera : MonoBehaviour {
Vector2?[] oldTouchPositions = {
null,
null
};
Vector2 oldTouchVector;
float oldTouchDistance;
void Update() {
if (Input.touchCount == 0) {
oldTouchPositions[0] = null;
oldTouchPositions[1] = null;
}
else if (Input.touchCount == 1) {
if (oldTouchPositions[0] == null || oldTouchPositions[1] != null) {
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = null;
}
else {
Vector2 newTouchPosition = Input.GetTouch(0).position;
transform.position += transform.TransformDirection((Vector3)((oldTouchPositions[0] - newTouchPosition) * GetComponent<Camera>().orthographicSize / GetComponent<Camera>().pixelHeight * 2f));
oldTouchPositions[0] = newTouchPosition;
}
}
else {
if (oldTouchPositions[1] == null) {
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = Input.GetTouch(1).position;
oldTouchVector = (Vector2)(oldTouchPositions[0] - oldTouchPositions[1]);
oldTouchDistance = oldTouchVector.magnitude;
}
else {
Vector2 screen = new Vector2(GetComponent<Camera>().pixelWidth, GetComponent<Camera>().pixelHeight);
Vector2[] newTouchPositions = {
Input.GetTouch(0).position,
Input.GetTouch(1).position
};
Vector2 newTouchVector = newTouchPositions[0] - newTouchPositions[1];
float newTouchDistance = newTouchVector.magnitude;
transform.position += transform.TransformDirection((Vector3)((oldTouchPositions[0] + oldTouchPositions[1] - screen) * GetComponent<Camera>().orthographicSize / screen.y));
transform.localRotation *= Quaternion.Euler(new Vector3(0, 0, Mathf.Asin(Mathf.Clamp((oldTouchVector.y * newTouchVector.x - oldTouchVector.x * newTouchVector.y) / oldTouchDistance / newTouchDistance, -1f, 1f)) / 0.0174532924f));
GetComponent<Camera>().orthographicSize *= oldTouchDistance / newTouchDistance;
transform.position -= transform.TransformDirection((newTouchPositions[0] + newTouchPositions[1] - screen) * GetComponent<Camera>().orthographicSize / screen.y);
oldTouchPositions[0] = newTouchPositions[0];
oldTouchPositions[1] = newTouchPositions[1];
oldTouchVector = newTouchVector;
oldTouchDistance = newTouchDistance;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ade537fc62d5c974cb84efe75fbc9f08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

15
Assets/Spinner.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.PlayerLoop;
public class Spinner : MonoBehaviour
{
private Vector3 speed = new Vector3(0, 1, 0);
private void Update()
{
transform.rotation = Quaternion.Euler(speed * Time.deltaTime);
}
}

11
Assets/Spinner.cs.meta Normal file
View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f8931783fc280145becdca83483472b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

37
Assets/StickerDisplay.cs Normal file
View File

@@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class StickerDisplay : MonoBehaviour
{
public TMP_Text label;
public TMP_Text duckCount;
public Image stickerImage;
private DuckStickerData _sticker;
public DuckStickerData Sticker
{
get { return _sticker; }
set
{
_sticker = value;
label.text = _sticker.Label;
stickerImage.sprite = _sticker.StickerSprite;
}
}
private int _collectedCount;
public int CollectedCount
{
get => _collectedCount;
set
{
_collectedCount = value;
duckCount.text = _collectedCount + "x";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3880ade5e4f047a4abdaa396235ba1cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/TextMesh Pro.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f54d1bd14bd3ca042bd867b519fee8cc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -8,55 +8,4 @@ EditorBuildSettings:
- enabled: 1 - enabled: 1
path: Assets/Scenes/Main.unity path: Assets/Scenes/Main.unity
guid: 767cffa59425a494fb8a2e25e1a54f3e guid: 767cffa59425a494fb8a2e25e1a54f3e
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_aura.unity
guid: 27f678a331798d34887cc39800f31171
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_aura2.unity
guid: b26d377b60afd7a4a8c9b54ba50b5460
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_beams.unity
guid: 0bc746c23ebc23f468cf026e404c8b09
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_charge.unity
guid: df3f445d429f4694dad011a48a1e8740
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_cleave.unity
guid: e08a222b2cbace24c9fae6c7cd048fce
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_domes.unity
guid: 86e32af37f1e5cf4f82353613901b838
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_enchant.unity
guid: 0c900609b41d1364abae8a160b34c3f0
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_fire.unity
guid: 401dd916c2513af48b9b42f49a61b758
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_meshglow.unity
guid: 9f87ba6aaa0e379439fd1ba7154ad6b3
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_modular.unity
guid: aea57987c632a0a4693d39bc0687de3d
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_pillarblast.unity
guid: b6d68d11f335cec41ac5052ee943ef3b
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_projectiles.unity
guid: 4d6f7df301810bd4b87d7ad934d35fbc
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_shields.unity
guid: 502bc141da8b3ea4da84b810979194fc
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_slash.unity
guid: 52be665b31be8bd43b7b167e1705ba19
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_sphereblast.unity
guid: 0d8da5f75a021ea428cb91c298ac7eee
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_sprays.unity
guid: 73f30dfb7f2bd764caed3119a48a505a
- enabled: 1
path: Assets/MagicArsenal/Demo/Scenes/magic_walls.unity
guid: 58f8db9124cc886449cfbcd267b324b1
m_configObjects: {} m_configObjects: {}

View File

@@ -12,7 +12,7 @@ PlayerSettings:
targetDevice: 2 targetDevice: 2
useOnDemandResources: 0 useOnDemandResources: 0
accelerometerFrequency: 60 accelerometerFrequency: 60
companyName: DefaultCompany companyName: Bas Dado
productName: Badeend GO productName: Badeend GO
defaultCursor: {fileID: 0} defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0} cursorHotspot: {x: 0, y: 0}
@@ -164,7 +164,7 @@ PlayerSettings:
applicationIdentifier: {} applicationIdentifier: {}
buildNumber: {} buildNumber: {}
AndroidBundleVersionCode: 1 AndroidBundleVersionCode: 1
AndroidMinSdkVersion: 16 AndroidMinSdkVersion: 24
AndroidTargetSdkVersion: 0 AndroidTargetSdkVersion: 0
AndroidPreferredInstallLocation: 1 AndroidPreferredInstallLocation: 1
aotOptions: aotOptions:
@@ -273,8 +273,107 @@ PlayerSettings:
AndroidValidateAppBundleSize: 1 AndroidValidateAppBundleSize: 1
AndroidAppBundleSizeToValidate: 150 AndroidAppBundleSizeToValidate: 150
resolutionDialogBanner: {fileID: 0} resolutionDialogBanner: {fileID: 0}
m_BuildTargetIcons: [] m_BuildTargetIcons:
m_BuildTargetPlatformIcons: [] - m_BuildTarget:
m_Icons:
- serializedVersion: 2
m_Icon: {fileID: 2800000, guid: e54c3889bfab2cf47869268ebb405f84, type: 3}
m_Width: 128
m_Height: 128
m_Kind: 0
m_BuildTargetPlatformIcons:
- m_BuildTarget: Android
m_Icons:
- m_Textures: []
m_Width: 192
m_Height: 192
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 144
m_Height: 144
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 96
m_Height: 96
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 72
m_Height: 72
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 48
m_Height: 48
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 36
m_Height: 36
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 192
m_Height: 192
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 144
m_Height: 144
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 96
m_Height: 96
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 72
m_Height: 72
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 48
m_Height: 48
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 36
m_Height: 36
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 432
m_Height: 432
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 324
m_Height: 324
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 216
m_Height: 216
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 162
m_Height: 162
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 108
m_Height: 108
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 81
m_Height: 81
m_Kind: 2
m_SubKind:
m_BuildTargetBatching: m_BuildTargetBatching:
- m_BuildTarget: Standalone - m_BuildTarget: Standalone
m_StaticBatching: 1 m_StaticBatching: 1
@@ -294,7 +393,7 @@ PlayerSettings:
m_BuildTargetGraphicsAPIs: m_BuildTargetGraphicsAPIs:
- m_BuildTarget: AndroidPlayer - m_BuildTarget: AndroidPlayer
m_APIs: 150000000b000000 m_APIs: 150000000b000000
m_Automatic: 0 m_Automatic: 1
- m_BuildTarget: iOSSupport - m_BuildTarget: iOSSupport
m_APIs: 10000000 m_APIs: 10000000
m_Automatic: 1 m_Automatic: 1
@@ -559,7 +658,8 @@ PlayerSettings:
scriptingRuntimeVersion: 1 scriptingRuntimeVersion: 1
gcIncremental: 0 gcIncremental: 0
gcWBarrierValidation: 0 gcWBarrierValidation: 0
apiCompatibilityLevelPerPlatform: {} apiCompatibilityLevelPerPlatform:
Android: 6
m_RenderingPath: 1 m_RenderingPath: 1
m_MobileRenderingPath: 1 m_MobileRenderingPath: 1
metroPackageName: Template_3D metroPackageName: Template_3D