Level selection buttons update

hazim-dev
Ali Sharoz 2 months ago
parent 0cffe27b7e
commit fc7269fc8c

@ -14,11 +14,11 @@ namespace D2D.Core
{
public enum CommonGameState
{
None,
None,
Running,
Menu
}
/// <summary>
/// Owner and its action. Used to unsub zero actions with already destroyed owners
/// </summary>
@ -27,7 +27,7 @@ namespace D2D.Core
public GameObject owner;
public Action action;
}
/// <summary>
/// Powerful and safe game state machine which extensible (use classes instead of fixed enums)
/// and allows to check is next state possible, count happened states
@ -37,29 +37,29 @@ namespace D2D.Core
public class GameStateMachine : MonoBehaviour, ILazy
{
[SerializeField] private CommonGameState _startState;
public bool IsEmpty => _states.Count == 0;
public bool IsGameFinished => WasAny<WinState, LoseState>();
public bool WasWin => Was<WinState>();
public bool WasLose => Was<LoseState>();
public GameState Last { get; private set; }
// Should be used only by editor
public IEnumerable<GameState> StatesForEditor => _states;
private List<GameState> _states = new List<GameState>();
/// <summary>
/// For each game state we have a list of subscribers' actions
/// </summary>
private Dictionary<Type, List<Subscriber>> _subscribersMap =
private Dictionary<Type, List<Subscriber>> _subscribersMap =
new Dictionary<Type, List<Subscriber>>();
private Dictionary<Type, GameState> _happenedStates =
private Dictionary<Type, GameState> _happenedStates =
new Dictionary<Type, GameState>();
private void Start()
@ -82,32 +82,32 @@ namespace D2D.Core
// Init dictionary if needed
if (!_subscribersMap.ContainsKey(typeof(TState)))
_subscribersMap[typeof(TState)] = new List<Subscriber>();
// Add new subscriber
var newSubscriber = new Subscriber
{
owner = owner,
action = action,
};
_subscribersMap[typeof(TState)].Add(newSubscriber);
}
/// <summary>
/// On with 2 game state binding.
/// </summary>
public void On<TState1, TState2>(Action action, GameObject owner = null)
public void On<TState1, TState2>(Action action, GameObject owner = null)
where TState1 : GameState
where TState2 : GameState
{
On<TState1>(action, owner);
On<TState2>(action, owner);
}
/// <summary>
/// On with 3 game state binding.
/// </summary>
public void On<TState1, TState2, TState3>(Action action, GameObject owner = null)
public void On<TState1, TState2, TState3>(Action action, GameObject owner = null)
where TState1 : GameState
where TState2 : GameState
where TState3 : GameState
@ -126,22 +126,32 @@ namespace D2D.Core
{
if (!safely)
Debug.LogError($"{newState} can't go after {Last}");
return;
}
_happenedStates[newState.GetType()] = newState;
_states.Add(newState);
Last = newState;
newState.Action();
NotifySubscribers(newState.GetType());
//if()
//DataManager.Instance.MAXLEVELINDEX = DataManager.Instance.MAXLEVELINDEX == DataManager.Instance.CURRENTLEVELINDEX ? DataManager.Instance.MAXLEVELINDEX + 1: DataManager.Instance.MAXLEVELINDEX;
DataManager.Instance.MAXLEVELINDEX =
(DataManager.Instance.MAXLEVELINDEX == DataManager.Instance.CURRENTLEVELINDEX)
? DataManager.Instance.MAXLEVELINDEX + 1
: DataManager.Instance.MAXLEVELINDEX;
DataManager.Instance.CURRENTLEVELINDEX++;
// For subs who are listening for every state
// Emit(typeof(GameState));
}
public void Push<TState>(bool safely = false) where TState : GameState
{
_happenedStates.TryGetValue(typeof(TState), out GameState g);
@ -162,14 +172,14 @@ namespace D2D.Core
// State should be different
if (!newState.CanRepeat && Last.GetType() == newState.GetType())
return false;
// Any new state after last scene loading??? Nee, it is not possible
if (Last.Is<SceneLoading>())
return false;
if (Last.Is<LoseState>() && newState.Is<WinState>())
return false;
if (Last.Is<WinState>() && newState.Is<LoseState>())
return false;
@ -183,10 +193,10 @@ namespace D2D.Core
{
if (!_subscribersMap.TryGetValue(t, out var subs))
return;
if (subs.IsNullOrEmpty())
return;
// For here cuz we will use remove at
for (int i = 0; i < subs.Count; i++)
{
@ -230,11 +240,11 @@ namespace D2D.Core
if (removingOwner == null)
return;
foreach (var typeSubscribersPair in _subscribersMap)
{
var subs = typeSubscribersPair.Value;
if (subs.IsNullOrEmpty())
continue;
@ -263,12 +273,12 @@ namespace D2D.Core
/// <summary>
/// Was this state emitted?
/// </summary>
public bool Was<TState>()
public bool Was<TState>()
where TState : GameState
{
return _happenedStates.ContainsKey(typeof(TState));
}
/// <summary>
/// Was any of these states emitted?
/// </summary>

@ -126,6 +126,7 @@ namespace D2D.Databases
protected override void OnGameWin()
{
Debug.Log("PassedLevels: " + PassedLevels.Value);
CompletedLevelsPerSession.Value++;
PassedLevels.Value++;
}

@ -26,6 +26,7 @@ namespace D2D
private void Show(GameObject[] parent)
{
Debug.Log("CheckAli");
if (parent.IsNullOrEmpty())
return;

@ -54,6 +54,7 @@ MonoBehaviour:
- StompyRobot.SRDebugger.Editor
- ToonyColorsPro.Runtime
- MoreMountains.Feedbacks.URP
- AssetUsageDetector.Editor
- NaughtyAttributes.Test
- MoreMountains.Feedbacks.MMTools
- Lofelt.NiceVibrations

@ -0,0 +1,52 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public enum LevelState { Locked,Completed,Upcoming}
public class LevelButton : MonoBehaviour
{
public LevelState levelState;
public List<GameObject> levelStateImages;
bool[] buttonState = { false, true, true };
public Button button;
public int id;
//private void OnValidate()
//{
// Func();
//}
//public void Func()
//{
// if(id==0)
// id = transform.GetSiblingIndex();
//}
private void Start()
{
CheckState();
StateEnabler();
}
public void CheckState()
{
if (DataManager.Instance.MAXLEVELINDEX < id)
{
levelState = LevelState.Locked;
}
else if (DataManager.Instance.MAXLEVELINDEX == id)
{
levelState = LevelState.Upcoming;
}
else
{
levelState = LevelState.Completed;
}
}
public void StateEnabler()
{
levelStateImages[(int)levelState].SetActive(true);
button.enabled = buttonState[(int)levelState];
}
public void currentLevelIndexSet()
{
DataManager.Instance.CURRENTLEVELINDEX = id;
}
}

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

@ -1,5 +1,6 @@
fileFormatVersion: 2
guid: 2a7477119085c0543ab993533aa56fad
guid: 86da4588b8b8cfa4a8ed40aa77329d3e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:

@ -0,0 +1,51 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.2] - 2023-01-10
- Fix icon offset influence on other inspector entries
## [1.2.1] - 2022-04-24
- Fix detection for PlayerPrefs where the key contains '_h'
- Use unicode for windows registry lookups to support none ASCII chars in projects names
## [1.2.0] - 2022-01-01
### Added
- Enhanced search field to filter player preferences by key or value
- Add sorting functionality for Pref entries (none, ascending, descending)
### Removed
- Remove Unity 2017 support
- Remove Unity 2018 support
## [1.1.2] - 2021-07-01
- Fixed ImageManger icon detection
## [1.1.1] - 2021-05-23
- Add utf8 key encryption support for windows
## [1.1.0] - 2021-05-17
- Improve key validation with more characters
- Async output reading for MAC plist process
- Performance optimizations
## [1.0.4] - 2020-09-20
- Add handling for special characters in product/company name
- Improvement of plist read call on MAC
## [1.0.3] - 2020-09-20
- Fix text color on professional skin
## [1.0.2] - 2020-08-11
- Switch package author to 'BG Tools'
- Fix UPM documentation image path
## [1.0.1] - 2020-06-01
- Resizable column width for table layout
- Multiple UX improvements
- Add manual
## [1.0.0] - 2020-05-26
This is the first release of PlayerPrefs Editor

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

@ -1,5 +1,6 @@
fileFormatVersion: 2
guid: 474d6b0a9f4e12747bf9c8ee63d2f12a
guid: ac7740c4463611344b22ca368af84da1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

@ -0,0 +1,92 @@
fileFormatVersion: 2
guid: 418213a0ca74fec47960ee53fbae1cad
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@ -0,0 +1,92 @@
fileFormatVersion: 2
guid: 34408ed3bf23927459af6b3263c3b147
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,474 @@
<!DOCTYPE html>
<html>
<head>
<title>PlayerPrefsEditor-Manual</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<style type="text/css">
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px;
color: #333;
}
body &gt; *:first-child {
margin-top: 0 !important;
}
body &gt; *:last-child {
margin-bottom: 0 !important;
}
a {
color: #4183C4;
text-decoration: none;
}
a.absent {
color: #cc0000;
}
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative;
}
h2:first-child, h1:first-child, h1:first-child + h2, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
margin-top: 0;
padding-top: 0;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
text-decoration: none;
}
h1 tt, h1 code {
font-size: inherit;
}
h2 tt, h2 code {
font-size: inherit;
}
h3 tt, h3 code {
font-size: inherit;
}
h4 tt, h4 code {
font-size: inherit;
}
h5 tt, h5 code {
font-size: inherit;
}
h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
color: black;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: #777777;
font-size: 14px;
}
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0;
}
/*
hr {
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
*/
body &gt; h2:first-child {
margin-top: 0;
padding-top: 0;
}
body &gt; h1:first-child {
margin-top: 0;
padding-top: 0;
}
body &gt; h1:first-child + h2 {
margin-top: 0;
padding-top: 0;
}
body &gt; h3:first-child, body &gt; h4:first-child, body &gt; h5:first-child, body &gt; h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
ul, ol {
padding-left: 30px;
}
ul :first-child, ol :first-child {
margin-top: 0;
}
ul :last-child, ol :last-child {
margin-bottom: 0;
}
dl {
padding: 0;
}
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}
dl dt:first-child {
padding: 0;
}
dl dt &gt; :first-child {
margin-top: 0;
}
dl dt &gt; :last-child {
margin-bottom: 0;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd &gt; :first-child {
margin-top: 0;
}
dl dd &gt; :last-child {
margin-bottom: 0;
}
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777;
}
blockquote &gt; :first-child {
margin-top: 0;
}
blockquote &gt; :last-child {
margin-bottom: 0;
}
table {
padding: 0;
}
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0;
}
table tr:nth-child(2n) {
background-color: #f8f8f8;
}
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr th :first-child, table tr td :first-child {
margin-top: 0;
}
table tr th :last-child, table tr td :last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
}
span.frame {
display: block;
overflow: hidden;
}
span.frame &gt; span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto;
}
span.frame span img {
display: block;
float: left;
}
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0;
}
span.align-center {
display: block;
overflow: hidden;
clear: both;
}
span.align-center &gt; span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center;
}
span.align-center span img {
margin: 0 auto;
text-align: center;
}
span.align-right {
display: block;
overflow: hidden;
clear: both;
}
span.align-right &gt; span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right;
}
span.align-right span img {
margin: 0;
text-align: right;
}
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left;
}
span.float-left span {
margin: 13px 0 0;
}
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right;
}
span.float-right &gt; span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right;
}
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
</style>
</head>
<body style="">
<h1 id="0">PlayerPrefs Editor for Unity 3D</h1>
<p id="2">Tool extension for the Unity Editor that enables easy access to the player preferences over a simple UI. Allows to view, add, remove and modify entries on the development machine.</p>
<h2 id="4">Support</h2>
<p id="6"><a href="https://github.com/Dysman/bgTools-playerPrefsEditor">GitHub</a> | <a href="https://www.bgtools.de/post/playerprefseditor/">Website</a> | <a href="mailto:support@bgtools.de">Mail</a> | <a href="https://discord.gg/8rcPZrD">Discord</a></p>
<h2 id="8">Features</h2>
<ul id="10">
<li id="10">Add, remove and edit PlayerPrefs</li>
<li id="11">Intuitive visual editor</li>
<li id="12">Works with standard Unity PlayerPrefs</li>
<li id="13">Monitors changes from code</li>
<li id="14">Supports all editors (Windows, Linux, MacOS)</li>
<li id="15">Lightweight dockable for full integration in your workflow</li>
<li id="16">Supports both skins (Personal, Professional)</li>
</ul>
<h2 id="18">Usage</h2>
<p id="20">The PlayerPrefs Editor is located in the top menu at Tools/BG Tools/PlayerPrefs Editor. It's a standard dockable window, so place it wherever it helps to be productive.</p>
<p id="22"><img src="./Images/bgtools_ppe_manual_layout.png" width="100%" style="max-width:800px" alt="PlayerPrefs editor window layout" /></p>
<p id="24">The PlayerPrefs Editor window displays:</p>
<ul id="25">
<li id="25">(A) Filter field</li>
<li id="26">(B) Sort mode</li>
<li id="27">(C) Toggle 'System changes monitoring' behavior</li>
<li id="28">(D) Refresh data</li>
<li id="29">(E) Delete all data</li>
<li id="30">(F) Operating system and path to PlayerPrefs data</li>
<li id="31">(G) PlayerPrefs data list (Key, Type, Value)</li>
<li id="32">(H) Add/Remove a PlayerPrefs entry</li>
<li id="33">(I) Toggle visibility of system defined PlayerPrefs</li>
</ul>
<h3 id="35">Modify Entries</h3>
<p id="37">The PlayerPrefs Editor allow to add, remove and edit PlayerPrefs data.</p>
<p id="39"><strong>Add a new entry</strong></p>
<p id="41">Press the '+' button and select the type of the new PlayerPref entry. It's not possible to modify this type later. Add the key for the new entry in the upcoming dialog field. Additionally the dialog will provide feedback when it detected unintended overrides or invalid characters.</p>
<p id="43"><strong>Remove a existing entry</strong></p>
<p id="45">Select the entry from the list that you want to delete. Press the '-' button to remove this entry. Confirm the warning dialog to finish the operation.</p>
<p id="47"><strong>Modify a existing entry</strong></p>
<p id="49">To change a value of a existing entry do this directly in the value field in the PlayerPref list.</p>
<h3 id="51">Sort &amp; Filter</h3>
<p id="53"><strong>Sorting</strong></p>
<p id="55">Circle trought the sorting funtions by pressing the (B) button in the toolbar.</p>
<p id="57">Following sorting function are aviliable for the PlayerPref entries:</p>
<ul id="58">
<li id="58">None</li>
<li id="59">Ascending</li>
<li id="60">Descending</li>
</ul>
<p id="62"><strong>Filtering</strong></p>
<p id="64">Enter a text into the the search field (A) in the toolbar to filter the PlayerPrefs data list (G).</p>
<p id="66"><img src="./Images/bgtools_ppe_manual_filterModes.png" width="50%" style="max-width:350px" alt="PlayerPrefs editor searchfield modes" /></p>
<p id="68">Additionally select the mode for filtering by pressing on the magnifying glass icon in the search field. Choose between filtering the existing PlayPrefs by key or value. The current search target will be shown in the searchfield if no search string is present.</p>
<h3 id="70">Monitoring system changes</h3>
<p id="72">The plugin can monitor changes at runtime automatically and keep the view up-to-date. This detection is active by default, but it can be turned off over the (C) button in the toolbar.</p>
<h2 id="74">Samples</h2>
<p id="76">This package includes two samples for testing purposes:</p>
<p id="78"><strong>Test Value Menu</strong></p>
<blockquote id="79">
<p id="79">Adds new entries into the top menu Tools/BG Tools/PlayerPrefs Test Values. This allows easily to add text values to the PlayerPrefs of the current project.</p>
</blockquote>
<p id="81"><strong>Sample Scene</strong></p>
<blockquote id="82">
<p id="82">Simple UI that manipulates PlayerPrefs entries on runtime.</p>
</blockquote>
<h2 id="84">Technical details</h2>
<h3 id="85">Requirements</h3>
<p id="87">This version of PlayerPrefs Editor is compatible with the following versions of the Unity Editor:</p>
<blockquote id="89">
<p id="89">2019.4 and later (recommended)<br />
Windows, MacOS, Linux</p>
</blockquote>
<h3 id="92">Limitations MacOS</h3>
<p id="94">Due to technical on MacOS it take time to update the persistent file that stores the PlayerPrefs. To avoid inconsistent data the plugin will show a loading screen until the data can be fully refreshed. Sorry for the inconvenience.</p>
<p id="96">Keep in mind that it's possible to deactivate the automatic refresh in the settings.</p>
</body>
<html>

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

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 31c463d7ccf40cf4cab8c990a851231d
folderAsset: yes
timeCreated: 1500321077
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,224 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace BgTools.Utils
{
public class ImageManager
{
// Keep this ID unique
private static readonly string ID = "[PlayerPrefsEditor] com.bgtools.playerprefseditor";
private static string imageManagerPath;
private static string GetAssetDir()
{
if (imageManagerPath != null)
{
return imageManagerPath;
}
foreach (string assetGuid in AssetDatabase.FindAssets("ImageManager"))
{
string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
string fileName = Path.GetFileName(assetPath);
if (fileName.Equals("ImageManager.cs"))
{
// Check ID if it's the correct ImageManager
if (File.ReadLines(Path.GetFullPath(assetPath)).Any(line => line.Contains(ID)))
{
imageManagerPath = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar;
return imageManagerPath;
}
}
}
throw new Exception("Cannot find ImageManager.cs in the project. Are sure all the files in place?");
}
public static Texture2D GetOsIcon()
{
#if UNITY_EDITOR_WIN
return OsWinIcon;
#elif UNITY_EDITOR_OSX
return OsMacIcon;
#elif UNITY_EDITOR_LINUX
return OsLinuxIcon;
#endif
}
private static Texture2D osLinuxIcon;
public static Texture2D OsLinuxIcon
{
get
{
if (osLinuxIcon == null)
{
osLinuxIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_linux_icon.png", typeof(Texture2D));
}
return osLinuxIcon;
}
}
private static Texture2D osWinIcon;
public static Texture2D OsWinIcon
{
get
{
if (osWinIcon == null)
{
osWinIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_win_icon.png", typeof(Texture2D));
}
return osWinIcon;
}
}
private static Texture2D osMacIcon;
public static Texture2D OsMacIcon
{
get
{
if (osMacIcon == null)
{
osMacIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_mac_icon.png", typeof(Texture2D));
}
return osMacIcon;
}
}
private static GUIContent[] spinWheelIcons;
public static GUIContent[] SpinWheelIcons
{
get
{
if(spinWheelIcons == null)
{
spinWheelIcons = new GUIContent[12];
for (int i = 0; i < 12; i++)
spinWheelIcons[i] = EditorGUIUtility.IconContent("WaitSpin" + i.ToString("00"));
}
return spinWheelIcons;
}
}
private static Texture2D refresh;
public static Texture2D Refresh
{
get
{
if (refresh == null)
{
refresh = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "refresh.png", typeof(Texture2D));
}
return refresh;
}
}
private static Texture2D trash;
public static Texture2D Trash
{
get
{
if (trash == null)
{
trash = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "trash.png", typeof(Texture2D));
}
return trash;
}
}
private static Texture2D exclamation;
public static Texture2D Exclamation
{
get
{
if(exclamation == null)
{
exclamation = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "exclamation.png", typeof(Texture2D));
}
return exclamation;
}
}
private static Texture2D info;
public static Texture2D Info
{
get
{
if (info == null)
{
info = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "info.png", typeof(Texture2D));
}
return info;
}
}
private static Texture2D watching;
public static Texture2D Watching
{
get
{
if(watching == null)
{
watching = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "watching.png", typeof(Texture2D));
}
return watching;
}
}
private static Texture2D notWatching;
public static Texture2D NotWatching
{
get
{
if (notWatching == null)
{
notWatching = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "not_watching.png", typeof(Texture2D));
}
return notWatching;
}
}
private static Texture2D sortDisabled;
public static Texture2D SortDisabled
{
get
{
if (sortDisabled == null)
{
sortDisabled = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort.png", typeof(Texture2D));
}
return sortDisabled;
}
}
private static Texture2D sortAsscending;
public static Texture2D SortAsscending
{
get
{
if (sortAsscending == null)
{
sortAsscending = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort_asc.png", typeof(Texture2D));
}
return sortAsscending;
}
}
private static Texture2D sortDescending;
public static Texture2D SortDescending
{
get
{
if (sortDescending == null)
{
sortDescending = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort_desc.png", typeof(Texture2D));
}
return sortDescending;
}
}
}
}
#endif

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ee68545419352384a950cc488e731084
timeCreated: 1500324006
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,14 @@
{
"name": "Unity.PlayerPrefsEditor.EditorResources",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

@ -0,0 +1,99 @@
fileFormatVersion: 2
guid: 058af12bb195cdc43a0f974953fc4afd
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- serializedVersion: 2
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

@ -0,0 +1,99 @@
fileFormatVersion: 2
guid: babe6cc749d43c2469c6cb12a7a9d344
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- serializedVersion: 2
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

@ -0,0 +1,86 @@
fileFormatVersion: 2
guid: 01600487ba432264983788be42b0b029
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

@ -0,0 +1,100 @@
fileFormatVersion: 2
guid: c78f517c9f87bdf4e814d0d96e740793
timeCreated: 1500327620
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: iPhone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

@ -0,0 +1,100 @@
fileFormatVersion: 2
guid: c94af9de33f2f524ca4141c5ea383090
timeCreated: 1500322442
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: iPhone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

@ -0,0 +1,100 @@
fileFormatVersion: 2
guid: d40b23b11d3b85145af46d2c1316e057
timeCreated: 1500327620
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: iPhone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

@ -0,0 +1,100 @@
fileFormatVersion: 2
guid: 7d9ae81a8b3252449820b277748395bc
timeCreated: 1500927179
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: iPhone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,122 @@
fileFormatVersion: 2
guid: 5d3ebd901d622c14a9653af004d172a0
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,108 @@
fileFormatVersion: 2
guid: 3e86075fa207f0041b6111dbfaa3d66e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,108 @@
fileFormatVersion: 2
guid: 73059e24567e2e647bb1d8280f28bf43
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

@ -0,0 +1,106 @@
fileFormatVersion: 2
guid: cc312847c3bca82428ff672fea7385c7
timeCreated: 1502315347
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: iPhone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

@ -0,0 +1,86 @@
fileFormatVersion: 2
guid: ed8c91adb8fc54f41aaff0986b083281
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7afdecdeaea3efc42b92ba335397568c
folderAsset: yes
timeCreated: 1496263422
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2c61139fc58134242bb4b9e6d9fabdc0
folderAsset: yes
timeCreated: 1502815237
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,139 @@
using BgTools.Extensions;
using BgTools.Utils;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace BgTools.Dialogs
{
public class TextFieldDialog : EditorWindow
{
[NonSerialized]
private string resultString = string.Empty;
[NonSerialized]
private Action<string> callback;
[NonSerialized]
private string description;
[NonSerialized]
private List<TextValidator> validatorList = new List<TextValidator>();
[NonSerialized]
private TextValidator errorValidator = null;
public static void OpenDialog(string title, string description, List<TextValidator> validatorList, Action<string> callback, EditorWindow targetWin = null)
{
TextFieldDialog window = ScriptableObject.CreateInstance<TextFieldDialog>();
window.name = "TextFieldDialog '" + title + "'";
window.titleContent = new GUIContent (title);
window.description = description;
window.callback = callback;
window.validatorList = validatorList;
window.position = new Rect(0, 0, 350, 140);
window.ShowUtility();
window.CenterOnWindow(targetWin);
window.Focus();
EditorWindow.FocusWindowIfItsOpen<TextFieldDialog>();
}
void OnGUI()
{
errorValidator = null;
Color defaultColor = GUI.contentColor;
GUILayout.Space(20);
EditorGUILayout.LabelField(description);
GUILayout.Space(20);
GUI.SetNextControlName(name+"_textInput");
resultString = EditorGUILayout.TextField(resultString, GUILayout.ExpandWidth(true));
// GUILayout.Space(20);
GUILayout.FlexibleSpace();
foreach(TextValidator val in validatorList)
{
if (!val.Validate(resultString))
{
errorValidator = val;
break;
}
}
bool lockOkButton = !(errorValidator != null && errorValidator.m_errorType == TextValidator.ErrorType.Error);
GUILayout.BeginHorizontal();
if(errorValidator != null)
{
switch (errorValidator.m_errorType)
{
case TextValidator.ErrorType.Info:
GUI.contentColor = Styles.Colors.Blue;
GUILayout.Box(new GUIContent(ImageManager.Info, errorValidator.m_failureMsg), Styles.icon);
break;
case TextValidator.ErrorType.Warning:
GUI.contentColor = Styles.Colors.Yellow;
GUILayout.Box(new GUIContent(ImageManager.Exclamation, errorValidator.m_failureMsg), Styles.icon);
break;
case TextValidator.ErrorType.Error:
GUI.contentColor = Styles.Colors.Red;
GUILayout.Box(new GUIContent(ImageManager.Exclamation, errorValidator.m_failureMsg), Styles.icon);
break;
}
GUI.contentColor = defaultColor;
}
GUILayout.FlexibleSpace();
if (GUILayout.Button("Cancel", GUILayout.Width(75.0f)))
this.Close();
GUI.enabled = lockOkButton;
if (GUILayout.Button("OK", GUILayout.Width(75.0f)))
{
callback(resultString);
Close();
}
GUI.enabled = true;
GUILayout.EndHorizontal();
GUILayout.Space(20);
// set focus only if element exist
try
{
EditorGUI.FocusTextInControl(name+"_textInput");
}
catch (MissingReferenceException)
{ }
if (Event.current != null && Event.current.isKey)
{
switch (Event.current.keyCode)
{
case KeyCode.Return:
if (lockOkButton)
{
callback(resultString);
Close();
}
break;
case KeyCode.Escape:
Close();
break;
}
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 380be3677d2e95144863ee00c051c1f2
timeCreated: 1500849296
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,64 @@
using System;
using System.Text.RegularExpressions;
namespace BgTools.Dialogs
{
public class TextValidator
{
public enum ErrorType
{
Invalid = -1,
Info = 0,
Warning = 1,
Error = 2
}
[NonSerialized]
public ErrorType m_errorType = ErrorType.Invalid;
[NonSerialized]
private string m_regEx = string.Empty;
[NonSerialized]
private Func<string, bool> m_validationFunction;
[NonSerialized]
public string m_failureMsg = string.Empty;
/// <summary>
/// Validator for TextFieldDialog based on regex.
/// </summary>
/// <param name="errorType">Categorie of the error.</param>
/// <param name="failureMsg">Message that described the reason why the validation fail.</param>
/// <param name="regEx">String with regular expression. It need to describe the valid state.</param>
public TextValidator(ErrorType errorType, string failureMsg, string regEx)
{
m_errorType = errorType;
m_failureMsg = failureMsg;
m_regEx = regEx;
}
/// <summary>
/// Validator for TextFieldDialog based on regex.
/// </summary>
/// <param name="errorType">Categorie of the error.</param>
/// <param name="failureMsg">Message that described the reason why the validation fail.</param>
/// <param name="validationFunction">Function that validate the input. Get the current input as string and need to return a bool. Nedd to return 'false' if the validation fails.</param>
public TextValidator(ErrorType errorType, string failureMsg, Func<string, bool> validationFunction)
{
m_errorType = errorType;
m_failureMsg = failureMsg;
m_validationFunction = validationFunction;
}
public bool Validate(string srcString)
{
if (m_regEx != string.Empty)
return Regex.IsMatch(srcString, m_regEx);
else if (m_validationFunction != null)
return m_validationFunction(srcString);
return false;
}
}
}

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

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 5c7bb3ee5362c0a40a707ade01e79972
folderAsset: yes
timeCreated: 1502876479
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,90 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace BgTools.Extensions
{
public static class Extensions
{
private static Type[] GetAllDerivedTypes(this AppDomain aAppDomain, Type aType)
{
var result = new List<Type>();
var assemblies = aAppDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
var types = assembly.GetTypes();
foreach (Type type in types)
{
if (type.IsSubclassOf(aType))
result.Add(type);
}
}
return result.ToArray();
}
public static Rect GetEditorMainWindowPos(EditorWindow relatedWin = null)
{
var containerWinType = AppDomain.CurrentDomain.GetAllDerivedTypes(typeof(ScriptableObject)).Where(t => t.Name == "ContainerWindow").FirstOrDefault();
if (containerWinType == null)
throw new MissingMemberException("Can't find internal type ContainerWindow. Maybe something has changed inside Unity");
var showModeField = containerWinType.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var positionProperty = containerWinType.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (showModeField == null || positionProperty == null)
throw new MissingFieldException("Can't find internal fields 'm_ShowMode' or 'position'. Maybe something has changed inside Unity");
var windows = Resources.FindObjectsOfTypeAll(containerWinType);
foreach (var win in windows)
{
var showmode = (int)showModeField.GetValue(win);
// Given window
//if (relatedWin != null && relatedWin.GetInstanceID() == win.GetInstanceID())
//{
// var pos = (Rect)positionProperty.GetValue(win, null);
// return pos;
//}
// Main window
if (showmode == 4)
{
var pos = (Rect)positionProperty.GetValue(win, null);
return pos;
}
}
throw new NotSupportedException("Can't find internal main window. Maybe something has changed inside Unity");
}
/// <summary>
/// Center the EditorWindow in front of the MainUnityWindow (support multi screens).
/// Kept the currend window sizes.
/// </summary>
public static void CenterOnMainWindow(this EditorWindow window)
{
CenterOnWindow(window, null);
}
/// <summary>
/// Center the EditorWindow in front of the given EditorWindow (support multi screens).
/// Kept the currend window sizes.
/// </summary>
/// <param name="relatedWin">Referance window for the positioning.</param>
public static void CenterOnWindow(this EditorWindow window, EditorWindow relatedWin)
{
var main = GetEditorMainWindowPos(relatedWin);
var pos = window.position;
float w = (main.width - pos.width) * 0.5f;
float h = (main.height - pos.height) * 0.5f;
pos.x = main.x + w;
pos.y = main.y + h;
window.position = pos;
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 274db1862ad1a1b4c80a2ed6558e05ec
timeCreated: 1502876542
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e93ef5c4e798b034bb024596113459cb
folderAsset: yes
timeCreated: 1505565882
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,36 @@
namespace BgTools.PlayerPrefsEditor
{
[System.Serializable]
public class PreferenceEntry
{
public enum PrefTypes
{
String = 0,
Int = 1,
Float = 2
}
public PrefTypes m_typeSelection;
public string m_key;
// Need diffrend ones for auto type selection of serilizedProerty
public string m_strValue;
public int m_intValue;
public float m_floatValue;
public string ValueAsString()
{
switch(m_typeSelection)
{
case PrefTypes.String:
return m_strValue;
case PrefTypes.Int:
return m_intValue.ToString();
case PrefTypes.Float:
return m_floatValue.ToString();
default:
return string.Empty;
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2ae9239fbddf12b4099b3cacc5301271
timeCreated: 1496684286
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,29 @@
using System.Collections.Generic;
using UnityEngine;
namespace BgTools.PlayerPrefsEditor
{
[System.Serializable]
public class PreferenceEntryHolder : ScriptableObject
{
public List<PreferenceEntry> userDefList;
public List<PreferenceEntry> unityDefList;
private void OnEnable()
{
hideFlags = HideFlags.DontSave;
if (userDefList == null)
userDefList = new List<PreferenceEntry>();
if (unityDefList == null)
unityDefList = new List<PreferenceEntry>();
}
public void ClearLists()
{
if (userDefList != null)
userDefList.Clear();
if (unityDefList != null)
unityDefList.Clear();
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d5d94a5263d6af0478dde8fb08a3dcb7
timeCreated: 1500316993
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,272 @@
using System;
using System.Linq;
#if UNITY_EDITOR_WIN
using Microsoft.Win32;
using System.Text;
#elif UNITY_EDITOR_OSX
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
#elif UNITY_EDITOR_LINUX
using System.IO;
using System.Xml;
using System.Xml.Linq;
#endif
namespace BgTools.PlayerPrefsEditor
{
public abstract class PreferanceStorageAccessor
{
protected string prefPath;
protected string[] cachedData = new string[0];
protected abstract void FetchKeysFromSystem();
protected PreferanceStorageAccessor(string pathToPrefs)
{
prefPath = pathToPrefs;
}
public string[] GetKeys(bool reloadData = true)
{
if (reloadData || cachedData.Length == 0)
{
FetchKeysFromSystem();
}
return cachedData;
}
public Action PrefEntryChangedDelegate;
protected bool ignoreNextChange = false;
public void IgnoreNextChange()
{
ignoreNextChange = true;
}
protected virtual void OnPrefEntryChanged()
{
if (ignoreNextChange)
{
ignoreNextChange = false;
return;
}
PrefEntryChangedDelegate();
}
public Action StartLoadingDelegate;
public Action StopLoadingDelegate;
public abstract void StartMonitoring();
public abstract void StopMonitoring();
public abstract bool IsMonitoring();
}
#if UNITY_EDITOR_WIN
public class WindowsPrefStorage : PreferanceStorageAccessor
{
RegistryMonitor monitor;
public WindowsPrefStorage(string pathToPrefs) : base(pathToPrefs)
{
monitor = new RegistryMonitor(RegistryHive.CurrentUser, prefPath);
monitor.RegChanged += new EventHandler(OnRegChanged);
}
private void OnRegChanged(object sender, EventArgs e)
{
OnPrefEntryChanged();
}
protected override void FetchKeysFromSystem()
{
cachedData = new string[0];
using (RegistryKey rootKey = Registry.CurrentUser.OpenSubKey(prefPath))
{
if (rootKey != null)
{
cachedData = rootKey.GetValueNames();
rootKey.Close();
}
}
// Clean <key>_h3320113488 nameing
cachedData = cachedData.Select((key) => { return key.Substring(0, key.LastIndexOf("_h", StringComparison.Ordinal)); }).ToArray();
EncodeAnsiInPlace();
}
public override void StartMonitoring()
{
monitor.Start();
}
public override void StopMonitoring()
{
monitor.Stop();
}
public override bool IsMonitoring()
{
return monitor.IsMonitoring;
}
private void EncodeAnsiInPlace()
{
Encoding utf8 = Encoding.UTF8;
Encoding ansi = Encoding.GetEncoding(1252);
for (int i = 0; i < cachedData.Length; i++)
{
cachedData[i] = utf8.GetString(ansi.GetBytes(cachedData[i]));
}
}
}
#elif UNITY_EDITOR_LINUX
public class LinuxPrefStorage : PreferanceStorageAccessor
{
FileSystemWatcher fileWatcher;
public LinuxPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
{
fileWatcher = new FileSystemWatcher();
fileWatcher.Path = Path.GetDirectoryName(prefPath);
fileWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
fileWatcher.Filter = "prefs";
fileWatcher.Changed += OnWatchedFileChanged;
}
protected override void FetchKeysFromSystem()
{
cachedData = new string[0];
if (File.Exists(prefPath))
{
XmlReaderSettings settings = new XmlReaderSettings();
XmlReader reader = XmlReader.Create(prefPath, settings);
XDocument doc = XDocument.Load(reader);
cachedData = doc.Element("unity_prefs").Elements().Select((e) => e.Attribute("name").Value).ToArray();
}
}
public override void StartMonitoring()
{
fileWatcher.EnableRaisingEvents = true;
}
public override void StopMonitoring()
{
fileWatcher.EnableRaisingEvents = false;
}
public override bool IsMonitoring()
{
return fileWatcher.EnableRaisingEvents;
}
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
{
OnPrefEntryChanged();
}
}
#elif UNITY_EDITOR_OSX
public class MacPrefStorage : PreferanceStorageAccessor
{
private FileSystemWatcher fileWatcher;
private DirectoryInfo prefsDirInfo;
private String prefsFileNameWithoutExtension;
public MacPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
{
prefsDirInfo = new DirectoryInfo(Path.GetDirectoryName(prefPath));
prefsFileNameWithoutExtension = Path.GetFileNameWithoutExtension(prefPath);
fileWatcher = new FileSystemWatcher();
fileWatcher.Path = Path.GetDirectoryName(prefPath);
fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
fileWatcher.Filter = Path.GetFileName(prefPath);
// MAC delete the old and create a new file instead of updating
fileWatcher.Created += OnWatchedFileChanged;
}
protected override void FetchKeysFromSystem()
{
// Workaround to avoid incomplete tmp phase from MAC OS
foreach (FileInfo info in prefsDirInfo.GetFiles())
{
// Check if tmp PlayerPrefs file exist
if (info.FullName.Contains(prefsFileNameWithoutExtension) && !info.FullName.EndsWith(".plist"))
{
StartLoadingDelegate();
return;
}
}
StopLoadingDelegate();
cachedData = new string[0];
if (File.Exists(prefPath))
{
string fixedPrefsPath = prefPath.Replace("\"", "\\\"").Replace("'", "\\'").Replace("`", "\\`");
var cmdStr = string.Format(@"-p '{0}'", fixedPrefsPath);
string stdOut = String.Empty;
string errOut = String.Empty;
var process = new System.Diagnostics.Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "plutil";
process.StartInfo.Arguments = cmdStr;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += new DataReceivedEventHandler((sender, evt) => { stdOut += evt.Data + "\n"; });
process.ErrorDataReceived += new DataReceivedEventHandler((sender, evt) => { errOut += evt.Data + "\n"; });
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
MatchCollection matches = Regex.Matches(stdOut, @"(?: "")(.*)(?:"" =>.*)");
cachedData = matches.Cast<Match>().Select((e) => e.Groups[1].Value).ToArray();
}
}
public override void StartMonitoring()
{
fileWatcher.EnableRaisingEvents = true;
}
public override void StopMonitoring()
{
fileWatcher.EnableRaisingEvents = false;
}
public override bool IsMonitoring()
{
return fileWatcher.EnableRaisingEvents;
}
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
{
OnPrefEntryChanged();
}
}
#endif
}

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

@ -0,0 +1,700 @@
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using BgTools.Utils;
using BgTools.Dialogs;
#if (UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX)
using System.Text;
using System.Globalization;
#endif
namespace BgTools.PlayerPrefsEditor
{
public class PreferencesEditorWindow : EditorWindow
{
#region ErrorValues
private readonly int ERROR_VALUE_INT = int.MinValue;
private readonly string ERROR_VALUE_STR = "<bgTool_error_24072017>";
#endregion //ErrorValues
private enum PreferencesEntrySortOrder
{
None = 0,
Asscending = 1,
Descending = 2
}
private static string pathToPrefs = String.Empty;
private static string platformPathPrefix = @"~";
private string[] userDef;
private string[] unityDef;
private bool showSystemGroup = false;
private PreferencesEntrySortOrder sortOrder = PreferencesEntrySortOrder.None;
private SerializedObject serializedObject;
private ReorderableList userDefList;
private ReorderableList unityDefList;
private SerializedProperty[] userDefListCache = new SerializedProperty[0];
private PreferenceEntryHolder prefEntryHolder;
private Vector2 scrollPos;
private float relSpliterPos;
private bool moveSplitterPos = false;
private PreferanceStorageAccessor entryAccessor;
private MySearchField searchfield;
private string searchTxt;
private int loadingSpinnerFrame;
private bool updateView = false;
private bool monitoring = false;
private bool showLoadingIndicatorOverlay = false;
private readonly List<TextValidator> prefKeyValidatorList = new List<TextValidator>()
{
new TextValidator(TextValidator.ErrorType.Error, @"Invalid character detected. Only letters, numbers, space and ,.;:<>_|!§$%&/()=?*+~#-]+$ are allowed", @"(^$)|(^[a-zA-Z0-9 ,.;:<>_|!§$%&/()=?*+~#-]+$)"),
new TextValidator(TextValidator.ErrorType.Warning, @"The given key already exist. The existing entry would be overwritten!", (key) => { return !PlayerPrefs.HasKey(key); })
};
#if UNITY_EDITOR_LINUX
private readonly char[] invalidFilenameChars = { '"', '\\', '*', '/', ':', '<', '>', '?', '|' };
#elif UNITY_EDITOR_OSX
private readonly char[] invalidFilenameChars = { '$', '%', '&', '\\', '/', ':', '<', '>', '|', '~' };
#endif
[MenuItem("Tools/BG Tools/PlayerPrefs Editor", false, 1)]
static void ShowWindow()
{
PreferencesEditorWindow window = EditorWindow.GetWindow<PreferencesEditorWindow>(false, "Prefs Editor");
window.minSize = new Vector2(270.0f, 300.0f);
window.name = "Prefs Editor";
//window.titleContent = EditorGUIUtility.IconContent("SettingsIcon"); // Icon
window.Show();
}
private void OnEnable()
{
#if UNITY_EDITOR_WIN
pathToPrefs = @"SOFTWARE\Unity\UnityEditor\" + PlayerSettings.companyName + @"\" + PlayerSettings.productName;
platformPathPrefix = @"<CurrentUser>";
entryAccessor = new WindowsPrefStorage(pathToPrefs);
#elif UNITY_EDITOR_OSX
pathToPrefs = @"Library/Preferences/unity." + MakeValidFileName(PlayerSettings.companyName) + "." + MakeValidFileName(PlayerSettings.productName) + ".plist";
entryAccessor = new MacPrefStorage(pathToPrefs);
entryAccessor.StartLoadingDelegate = () => { showLoadingIndicatorOverlay = true; };
entryAccessor.StopLoadingDelegate = () => { showLoadingIndicatorOverlay = false; };
#elif UNITY_EDITOR_LINUX
pathToPrefs = @".config/unity3d/" + MakeValidFileName(PlayerSettings.companyName) + "/" + MakeValidFileName(PlayerSettings.productName) + "/prefs";
entryAccessor = new LinuxPrefStorage(pathToPrefs);
#endif
entryAccessor.PrefEntryChangedDelegate = () => { updateView = true; };
monitoring = EditorPrefs.GetBool("BGTools.PlayerPrefsEditor.WatchingForChanges", true);
if(monitoring)
entryAccessor.StartMonitoring();
sortOrder = (PreferencesEntrySortOrder) EditorPrefs.GetInt("BGTools.PlayerPrefsEditor.SortOrder", 0);
searchfield = new MySearchField();
searchfield.DropdownSelectionDelegate = () => { PrepareData(); };
// Fix for serialisation issue of static fields
if (userDefList == null)
{
InitReorderedList();
PrepareData();
}
}
// Handel view updates for monitored changes
// Necessary to avoid main thread access issue
private void Update()
{
if (showLoadingIndicatorOverlay)
{
loadingSpinnerFrame = (int)Mathf.Repeat(Time.realtimeSinceStartup * 10, 11.99f);
PrepareData();
Repaint();
}
if (updateView)
{
updateView = false;
PrepareData();
Repaint();
}
}
private void OnDisable()
{
entryAccessor.StopMonitoring();
}
private void InitReorderedList()
{
if (prefEntryHolder == null)
{
var tmp = Resources.FindObjectsOfTypeAll<PreferenceEntryHolder>();
if (tmp.Length > 0)
{
prefEntryHolder = tmp[0];
}
else
{
prefEntryHolder = ScriptableObject.CreateInstance<PreferenceEntryHolder>();
}
}
if (serializedObject == null)
{
serializedObject = new SerializedObject(prefEntryHolder);
}
userDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("userDefList"), false, true, true, true);
unityDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("unityDefList"), false, true, false, false);
relSpliterPos = EditorPrefs.GetFloat("BGTools.PlayerPrefsEditor.RelativeSpliterPosition", 100 / position.width);
userDefList.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "User defined");
};
userDefList.drawElementBackgroundCallback = OnDrawElementBackgroundCallback;
userDefList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
SerializedProperty element = GetUserDefListElementAtIndex(index, userDefList.serializedProperty);
SerializedProperty key = element.FindPropertyRelative("m_key");
SerializedProperty type = element.FindPropertyRelative("m_typeSelection");
SerializedProperty value;
// Load only necessary type
switch ((PreferenceEntry.PrefTypes)type.enumValueIndex)
{
case PreferenceEntry.PrefTypes.Float:
value = element.FindPropertyRelative("m_floatValue");
break;
case PreferenceEntry.PrefTypes.Int:
value = element.FindPropertyRelative("m_intValue");
break;
case PreferenceEntry.PrefTypes.String:
value = element.FindPropertyRelative("m_strValue");
break;
default:
value = element.FindPropertyRelative("This should never happen");
break;
}
float spliterPos = relSpliterPos * rect.width;
rect.y += 2;
EditorGUI.BeginChangeCheck();
string prefKeyName = key.stringValue;
EditorGUI.LabelField(new Rect(rect.x, rect.y, spliterPos - 1, EditorGUIUtility.singleLineHeight), new GUIContent(prefKeyName, prefKeyName));
GUI.enabled = false;
EditorGUI.EnumPopup(new Rect(rect.x + spliterPos + 1, rect.y, 60, EditorGUIUtility.singleLineHeight), (PreferenceEntry.PrefTypes)type.enumValueIndex);
GUI.enabled = !showLoadingIndicatorOverlay;
switch ((PreferenceEntry.PrefTypes)type.enumValueIndex)
{
case PreferenceEntry.PrefTypes.Float:
EditorGUI.DelayedFloatField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
case PreferenceEntry.PrefTypes.Int:
EditorGUI.DelayedIntField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
case PreferenceEntry.PrefTypes.String:
EditorGUI.DelayedTextField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
}
if (EditorGUI.EndChangeCheck())
{
entryAccessor.IgnoreNextChange();
switch ((PreferenceEntry.PrefTypes)type.enumValueIndex)
{
case PreferenceEntry.PrefTypes.Float:
PlayerPrefs.SetFloat(key.stringValue, value.floatValue);
break;
case PreferenceEntry.PrefTypes.Int:
PlayerPrefs.SetInt(key.stringValue, value.intValue);
break;
case PreferenceEntry.PrefTypes.String:
PlayerPrefs.SetString(key.stringValue, value.stringValue);
break;
}
PlayerPrefs.Save();
}
};
userDefList.onRemoveCallback = (ReorderableList l) =>
{
userDefList.ReleaseKeyboardFocus();
unityDefList.ReleaseKeyboardFocus();
string prefKey = l.serializedProperty.GetArrayElementAtIndex(l.index).FindPropertyRelative("m_key").stringValue;
if (EditorUtility.DisplayDialog("Warning!", $"Are you sure you want to delete this entry from PlayerPrefs?\n\nEntry: {prefKey}", "Yes", "No"))
{
entryAccessor.IgnoreNextChange();
PlayerPrefs.DeleteKey(prefKey);
PlayerPrefs.Save();
ReorderableList.defaultBehaviours.DoRemoveButton(l);
PrepareData();
GUIUtility.ExitGUI();
}
};
userDefList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) =>
{
var menu = new GenericMenu();
foreach (PreferenceEntry.PrefTypes type in Enum.GetValues(typeof(PreferenceEntry.PrefTypes)))
{
menu.AddItem(new GUIContent(type.ToString()), false, () =>
{
TextFieldDialog.OpenDialog("Create new property", "Key for the new property:", prefKeyValidatorList, (key) => {
entryAccessor.IgnoreNextChange();
switch (type)
{
case PreferenceEntry.PrefTypes.Float:
PlayerPrefs.SetFloat(key, 0.0f);
break;
case PreferenceEntry.PrefTypes.Int:
PlayerPrefs.SetInt(key, 0);
break;
case PreferenceEntry.PrefTypes.String:
PlayerPrefs.SetString(key, string.Empty);
break;
}
PlayerPrefs.Save();
PrepareData();
Focus();
}, this);
});
}
menu.ShowAsContext();
};
unityDefList.drawElementBackgroundCallback = OnDrawElementBackgroundCallback;
unityDefList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = unityDefList.serializedProperty.GetArrayElementAtIndex(index);
SerializedProperty key = element.FindPropertyRelative("m_key");
SerializedProperty type = element.FindPropertyRelative("m_typeSelection");
SerializedProperty value;
// Load only necessary type
switch ((PreferenceEntry.PrefTypes)type.enumValueIndex)
{
case PreferenceEntry.PrefTypes.Float:
value = element.FindPropertyRelative("m_floatValue");
break;
case PreferenceEntry.PrefTypes.Int:
value = element.FindPropertyRelative("m_intValue");
break;
case PreferenceEntry.PrefTypes.String:
value = element.FindPropertyRelative("m_strValue");
break;
default:
value = element.FindPropertyRelative("This should never happen");
break;
}
float spliterPos = relSpliterPos * rect.width;
rect.y += 2;
GUI.enabled = false;
string prefKeyName = key.stringValue;
EditorGUI.LabelField(new Rect(rect.x, rect.y, spliterPos - 1, EditorGUIUtility.singleLineHeight), new GUIContent(prefKeyName, prefKeyName));
EditorGUI.EnumPopup(new Rect(rect.x + spliterPos + 1, rect.y, 60, EditorGUIUtility.singleLineHeight), (PreferenceEntry.PrefTypes)type.enumValueIndex);
switch ((PreferenceEntry.PrefTypes)type.enumValueIndex)
{
case PreferenceEntry.PrefTypes.Float:
EditorGUI.DelayedFloatField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
case PreferenceEntry.PrefTypes.Int:
EditorGUI.DelayedIntField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
case PreferenceEntry.PrefTypes.String:
EditorGUI.DelayedTextField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none);
break;
}
GUI.enabled = !showLoadingIndicatorOverlay;
};
unityDefList.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Unity defined");
};
}
private void OnDrawElementBackgroundCallback(Rect rect, int index, bool isActive, bool isFocused)
{
if (Event.current.type == EventType.Repaint)
{
ReorderableList.defaultBehaviours.elementBackground.Draw(rect, false, isActive, isActive, isFocused);
}
Rect spliterRect = new Rect(rect.x + relSpliterPos * rect.width, rect.y, 2, rect.height);
EditorGUIUtility.AddCursorRect(spliterRect, MouseCursor.ResizeHorizontal);
if (Event.current.type == EventType.MouseDown && spliterRect.Contains(Event.current.mousePosition))
{
moveSplitterPos = true;
}
if(moveSplitterPos)
{
if (Event.current.mousePosition.x > 100 && Event.current.mousePosition.x<rect.width - 120)
{
relSpliterPos = Event.current.mousePosition.x / rect.width;
Repaint();
}
}
if (Event.current.type == EventType.MouseUp)
{
moveSplitterPos = false;
EditorPrefs.SetFloat("BGTools.PlayerPrefsEditor.RelativeSpliterPosition", relSpliterPos);
}
}
void OnGUI()
{
// Need to catch 'Stack empty' error on linux
try
{
if (showLoadingIndicatorOverlay)
{
GUI.enabled = false;
}
Color defaultColor = GUI.contentColor;
if (!EditorGUIUtility.isProSkin)
{
GUI.contentColor = Styles.Colors.DarkGray;
}
GUILayout.BeginVertical();
GUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUI.BeginChangeCheck();
searchTxt = searchfield.OnToolbarGUI(searchTxt);
if (EditorGUI.EndChangeCheck())
{
PrepareData(false);
}
GUILayout.FlexibleSpace();
EditorGUIUtility.SetIconSize(new Vector2(14.0f, 14.0f));
GUIContent sortOrderContent;
switch (sortOrder)
{
case PreferencesEntrySortOrder.Asscending:
sortOrderContent = new GUIContent(ImageManager.SortAsscending, "Ascending sorted");
break;
case PreferencesEntrySortOrder.Descending:
sortOrderContent = new GUIContent(ImageManager.SortDescending, "Descending sorted");
break;
case PreferencesEntrySortOrder.None:
default:
sortOrderContent = new GUIContent(ImageManager.SortDisabled, "Not sorted");
break;
}
if (GUILayout.Button(sortOrderContent, EditorStyles.toolbarButton))
{
sortOrder++;
if((int) sortOrder >= Enum.GetValues(typeof(PreferencesEntrySortOrder)).Length)
{
sortOrder = 0;
}
EditorPrefs.SetInt("BGTools.PlayerPrefsEditor.SortOrder", (int) sortOrder);
PrepareData(false);
}
GUIContent watcherContent = (entryAccessor.IsMonitoring()) ? new GUIContent(ImageManager.Watching, "Watching changes") : new GUIContent(ImageManager.NotWatching, "Not watching changes");
if (GUILayout.Button(watcherContent, EditorStyles.toolbarButton))
{
monitoring = !monitoring;
EditorPrefs.SetBool("BGTools.PlayerPrefsEditor.WatchingForChanges", monitoring);
if (monitoring)
entryAccessor.StartMonitoring();
else
entryAccessor.StopMonitoring();
Repaint();
}
if (GUILayout.Button(new GUIContent(ImageManager.Refresh, "Refresh"), EditorStyles.toolbarButton))
{
PlayerPrefs.Save();
PrepareData();
}
if (GUILayout.Button(new GUIContent(ImageManager.Trash, "Delete all"), EditorStyles.toolbarButton))
{
if (EditorUtility.DisplayDialog("Warning!", "Are you sure you want to delete ALL entries from PlayerPrefs?\n\nUse with caution! Unity defined keys are affected too.", "Yes", "No"))
{
PlayerPrefs.DeleteAll();
PrepareData();
GUIUtility.ExitGUI();
}
}
EditorGUIUtility.SetIconSize(new Vector2(0.0f, 0.0f));
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Box(ImageManager.GetOsIcon(), Styles.icon);
GUILayout.TextField(platformPathPrefix + Path.DirectorySeparatorChar + pathToPrefs, GUILayout.MinWidth(200));
GUILayout.EndHorizontal();
scrollPos = GUILayout.BeginScrollView(scrollPos);
serializedObject.Update();
userDefList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
GUILayout.FlexibleSpace();
showSystemGroup = EditorGUILayout.Foldout(showSystemGroup, new GUIContent("Show System"));
if (showSystemGroup)
{
unityDefList.DoLayoutList();
}
GUILayout.EndScrollView();
GUILayout.EndVertical();
GUI.enabled = true;
if (showLoadingIndicatorOverlay)
{
GUILayout.BeginArea(new Rect(position.size.x * 0.5f - 30, position.size.y * 0.5f - 25, 60, 50), GUI.skin.box);
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Box(ImageManager.SpinWheelIcons[loadingSpinnerFrame], Styles.icon);
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label("Loading");
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
GUILayout.EndArea();
}
GUI.contentColor = defaultColor;
}
catch (InvalidOperationException)
{ }
}
private void PrepareData(bool reloadKeys = true)
{
prefEntryHolder.ClearLists();
LoadKeys(out userDef, out unityDef, reloadKeys);
CreatePrefEntries(userDef, ref prefEntryHolder.userDefList);
CreatePrefEntries(unityDef, ref prefEntryHolder.unityDefList);
// Clear cache
userDefListCache = new SerializedProperty[prefEntryHolder.userDefList.Count];
}
private void CreatePrefEntries(string[] keySource, ref List<PreferenceEntry> listDest)
{
if (!string.IsNullOrEmpty(searchTxt) && searchfield.SearchMode == MySearchField.SearchModePreferencesEditorWindow.Key)
{
keySource = keySource.Where((keyEntry) => keyEntry.ToLower().Contains(searchTxt.ToLower())).ToArray();
}
foreach (string key in keySource)
{
var entry = new PreferenceEntry();
entry.m_key = key;
string s = PlayerPrefs.GetString(key, ERROR_VALUE_STR);
if (s != ERROR_VALUE_STR)
{
entry.m_strValue = s;
entry.m_typeSelection = PreferenceEntry.PrefTypes.String;
listDest.Add(entry);
continue;
}
float f = PlayerPrefs.GetFloat(key, float.NaN);
if (!float.IsNaN(f))
{
entry.m_floatValue = f;
entry.m_typeSelection = PreferenceEntry.PrefTypes.Float;
listDest.Add(entry);
continue;
}
int i = PlayerPrefs.GetInt(key, ERROR_VALUE_INT);
if (i != ERROR_VALUE_INT)
{
entry.m_intValue = i;
entry.m_typeSelection = PreferenceEntry.PrefTypes.Int;
listDest.Add(entry);
continue;
}
}
if (!string.IsNullOrEmpty(searchTxt) && searchfield.SearchMode == MySearchField.SearchModePreferencesEditorWindow.Value)
{
listDest = listDest.Where((preferenceEntry) => preferenceEntry.ValueAsString().ToLower().Contains(searchTxt.ToLower())).ToList<PreferenceEntry>();
}
switch(sortOrder)
{
case PreferencesEntrySortOrder.Asscending:
listDest.Sort((PreferenceEntry x, PreferenceEntry y) => { return x.m_key.CompareTo(y.m_key); });
break;
case PreferencesEntrySortOrder.Descending:
listDest.Sort((PreferenceEntry x, PreferenceEntry y) => { return y.m_key.CompareTo(x.m_key); });
break;
}
}
private void LoadKeys(out string[] userDef, out string[] unityDef, bool reloadKeys)
{
string[] keys = entryAccessor.GetKeys(reloadKeys);
//keys.ToList().ForEach( e => { Debug.Log(e); } );
// Seperate keys int unity defined and user defined
Dictionary<bool, List<string>> groups = keys
.GroupBy( (key) => key.StartsWith("unity.") || key.StartsWith("UnityGraphicsQuality") )
.ToDictionary( (g) => g.Key, (g) => g.ToList() );
unityDef = (groups.ContainsKey(true)) ? groups[true].ToArray() : new string[0];
userDef = (groups.ContainsKey(false)) ? groups[false].ToArray() : new string[0];
}
private SerializedProperty GetUserDefListElementAtIndex(int index, SerializedProperty ListProperty)
{
UnityEngine.Assertions.Assert.IsTrue(ListProperty.isArray, "Given 'ListProperts' is not type of array");
if (userDefListCache[index] == null)
{
userDefListCache[index] = ListProperty.GetArrayElementAtIndex(index);
}
return userDefListCache[index];
}
#if (UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX)
private string MakeValidFileName(string unsafeFileName)
{
string normalizedFileName = unsafeFileName.Trim().Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
// We need to use a TextElementEmumerator in order to support UTF16 characters that may take up more than one char(case 1169358)
TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(normalizedFileName);
while (charEnum.MoveNext())
{
string c = charEnum.GetTextElement();
if (c.Length == 1 && invalidFilenameChars.Contains(c[0]))
{
stringBuilder.Append('_');
continue;
}
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c, 0);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
stringBuilder.Append(c);
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
#endif
}
}
public class MySearchField : SearchField
{
public enum SearchModePreferencesEditorWindow { Key, Value }
public SearchModePreferencesEditorWindow SearchMode { get; private set; }
public Action DropdownSelectionDelegate;
public new string OnGUI(
Rect rect,
string text,
GUIStyle style,
GUIStyle cancelButtonStyle,
GUIStyle emptyCancelButtonStyle)
{
style.padding.left = 17;
Rect ContextMenuRect = new Rect(rect.x, rect.y, 10, rect.height);
// Add interactive area
EditorGUIUtility.AddCursorRect(ContextMenuRect, MouseCursor.Text);
if (Event.current.type == EventType.MouseDown && ContextMenuRect.Contains(Event.current.mousePosition))
{
void OnDropdownSelection(object parameter)
{
SearchMode = (SearchModePreferencesEditorWindow) Enum.Parse(typeof(SearchModePreferencesEditorWindow), parameter.ToString());
DropdownSelectionDelegate();
}
GenericMenu menu = new GenericMenu();
foreach(SearchModePreferencesEditorWindow EnumIt in Enum.GetValues(typeof(SearchModePreferencesEditorWindow)))
{
String EnumName = Enum.GetName(typeof(SearchModePreferencesEditorWindow), EnumIt);
menu.AddItem(new GUIContent(EnumName), SearchMode == EnumIt, OnDropdownSelection, EnumName);
}
menu.DropDown(rect);
}
// Render original search field
String result = base.OnGUI(rect, text, style, cancelButtonStyle, emptyCancelButtonStyle);
// Render additional images
GUIStyle ContexMenuOverlayStyle = GUIStyle.none;
ContexMenuOverlayStyle.contentOffset = new Vector2(9, 5);
GUI.Box(new Rect(rect.x, rect.y, 5, 5), EditorGUIUtility.IconContent("d_ProfilerTimelineDigDownArrow@2x"), ContexMenuOverlayStyle);
if (!HasFocus() && String.IsNullOrEmpty(text))
{
GUI.enabled = false;
GUI.Label(new Rect(rect.x + 14, rect.y, 40, rect.height), Enum.GetName(typeof(SearchModePreferencesEditorWindow), SearchMode));
GUI.enabled = true;
}
ContexMenuOverlayStyle.contentOffset = new Vector2();
return result;
}
public new string OnToolbarGUI(string text, params GUILayoutOption[] options) => this.OnToolbarGUI(GUILayoutUtility.GetRect(29f, 200f, 18f, 18f, EditorStyles.toolbarSearchField, options), text);
public new string OnToolbarGUI(Rect rect, string text) => this.OnGUI(rect, text, EditorStyles.toolbarSearchField, EditorStyles.toolbarButton, EditorStyles.toolbarButton);
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 13c94fa190e7e6f4690cadc347a312aa
timeCreated: 1496263475
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,364 @@
/*
* Thanks to gr0ss for the inspiration.
*
* https://github.com/gr0ss/RegistryMonitor
*
* 11/08/2019
*/
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace BgTools.PlayerPrefsEditor
{
public class RegistryMonitor : IDisposable
{
#region P/Invoke
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegCloseKey(IntPtr hKey);
private const int KEY_QUERY_VALUE = 0x0001;
private const int KEY_NOTIFY = 0x0010;
private const int STANDARD_RIGHTS_READ = 0x00020000;
private static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000));
private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001));
private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002));
private static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003));
private static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004));
private static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005));
private static readonly IntPtr HKEY_DYN_DATA = new IntPtr(unchecked((int)0x80000006));
#endregion
#region Event handling
/// <summary>
/// Occurs when the specified registry key has changed.
/// </summary>
public event EventHandler RegChanged;
/// <summary>
/// Raises the <see cref="RegChanged"/> event.
/// </summary>
/// <remarks>
/// <p>
/// <b>OnRegChanged</b> is called when the specified registry key has changed.
/// </p>
/// <note type="inheritinfo">
/// When overriding <see cref="OnRegChanged"/> in a derived class, be sure to call
/// the base class's <see cref="OnRegChanged"/> method.
/// </note>
/// </remarks>
protected virtual void OnRegChanged()
{
EventHandler handler = RegChanged;
if (handler != null)
handler(this, null);
}
/// <summary>
/// Occurs when the access to the registry fails.
/// </summary>
public event ErrorEventHandler Error;
/// <summary>
/// Raises the <see cref="Error"/> event.
/// </summary>
/// <param name="e">The <see cref="Exception"/> which occured while watching the registry.</param>
/// <remarks>
/// <p>
/// <b>OnError</b> is called when an exception occurs while watching the registry.
/// </p>
/// <note type="inheritinfo">
/// When overriding <see cref="OnError"/> in a derived class, be sure to call
/// the base class's <see cref="OnError"/> method.
/// </note>
/// </remarks>
protected virtual void OnError(Exception e)
{
ErrorEventHandler handler = Error;
if (handler != null)
handler(this, new ErrorEventArgs(e));
}
#endregion
#region Private member variables
private IntPtr _registryHive;
private string _registrySubName;
private object _threadLock = new object();
private Thread _thread;
private bool _disposed = false;
private ManualResetEvent _eventTerminate = new ManualResetEvent(false);
private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute | RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
/// </summary>
/// <param name="registryKey">The registry key to monitor.</param>
public RegistryMonitor(RegistryKey registryKey)
{
InitRegistryKey(registryKey.Name);
}
/// <summary>
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
/// </summary>
/// <param name="name">The name.</param>
public RegistryMonitor(string name)
{
if (name == null || name.Length == 0)
throw new ArgumentNullException("name");
InitRegistryKey(name);
}
/// <summary>
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
/// </summary>
/// <param name="registryHive">The registry hive.</param>
/// <param name="subKey">The sub key.</param>
public RegistryMonitor(RegistryHive registryHive, string subKey)
{
InitRegistryKey(registryHive, subKey);
}
/// <summary>
/// Disposes this object.
/// </summary>
public void Dispose()
{
Stop();
_disposed = true;
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets or sets the <see cref="RegChangeNotifyFilter">RegChangeNotifyFilter</see>.
/// </summary>
public RegChangeNotifyFilter RegChangeNotifyFilter
{
get { return _regFilter; }
set
{
lock (_threadLock)
{
if (IsMonitoring)
throw new InvalidOperationException("Monitoring thread is already running");
_regFilter = value;
}
}
}
#region Initialization
private void InitRegistryKey(RegistryHive hive, string name)
{
switch (hive)
{
case RegistryHive.ClassesRoot:
_registryHive = HKEY_CLASSES_ROOT;
break;
case RegistryHive.CurrentConfig:
_registryHive = HKEY_CURRENT_CONFIG;
break;
case RegistryHive.CurrentUser:
_registryHive = HKEY_CURRENT_USER;
break;
case RegistryHive.DynData:
_registryHive = HKEY_DYN_DATA;
break;
case RegistryHive.LocalMachine:
_registryHive = HKEY_LOCAL_MACHINE;
break;
case RegistryHive.PerformanceData:
_registryHive = HKEY_PERFORMANCE_DATA;
break;
case RegistryHive.Users:
_registryHive = HKEY_USERS;
break;
default:
throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive));
}
_registrySubName = name;
}
private void InitRegistryKey(string name)
{
string[] nameParts = name.Split('\\');
switch (nameParts[0])
{
case "HKEY_CLASSES_ROOT":
case "HKCR":
_registryHive = HKEY_CLASSES_ROOT;
break;
case "HKEY_CURRENT_USER":
case "HKCU":
_registryHive = HKEY_CURRENT_USER;
break;
case "HKEY_LOCAL_MACHINE":
case "HKLM":
_registryHive = HKEY_LOCAL_MACHINE;
break;
case "HKEY_USERS":
_registryHive = HKEY_USERS;
break;
case "HKEY_CURRENT_CONFIG":
_registryHive = HKEY_CURRENT_CONFIG;
break;
default:
_registryHive = IntPtr.Zero;
throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", "value");
}
_registrySubName = String.Join("\\", nameParts, 1, nameParts.Length - 1);
}
#endregion
/// <summary>
/// <b>true</b> if this <see cref="RegistryMonitor"/> object is currently monitoring;
/// otherwise, <b>false</b>.
/// </summary>
public bool IsMonitoring
{
get { return _thread != null; }
}
/// <summary>
/// Start monitoring.
/// </summary>
public void Start()
{
if (_disposed)
throw new ObjectDisposedException(null, "This instance is already disposed");
lock (_threadLock)
{
if (!IsMonitoring)
{
_eventTerminate.Reset();
_thread = new Thread(new ThreadStart(MonitorThread)) { IsBackground = true };
_thread.Start();
}
}
}
/// <summary>
/// Stops the monitoring thread.
/// </summary>
public void Stop()
{
if (_disposed)
throw new ObjectDisposedException(null, "This instance is already disposed");
lock (_threadLock)
{
Thread thread = _thread;
if (thread != null)
{
_eventTerminate.Set();
thread.Join();
}
}
}
private void MonitorThread()
{
try
{
ThreadLoop();
}
catch (Exception e)
{
OnError(e);
}
_thread = null;
}
private void ThreadLoop()
{
IntPtr registryKey;
int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out registryKey);
if (result != 0)
{
throw new Win32Exception(result);
}
try
{
AutoResetEvent _eventNotify = new AutoResetEvent(false);
WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate };
while (!_eventTerminate.WaitOne(0, true))
{
result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.SafeWaitHandle.DangerousGetHandle(), true);
if (result != 0)
{
throw new Win32Exception(result);
}
if (WaitHandle.WaitAny(waitHandles) == 0)
{
OnRegChanged();
}
}
}
finally
{
if (registryKey != IntPtr.Zero)
{
RegCloseKey(registryKey);
}
}
}
}
/// <summary>
/// Filter for notifications reported by <see cref="RegistryMonitor"/>.
/// </summary>
[Flags]
public enum RegChangeNotifyFilter
{
/// <summary>Notify the caller if a subkey is added or deleted.</summary>
Key = 1,
/// <summary>Notify the caller of changes to the attributes of the key,
/// such as the security descriptor information.</summary>
Attribute = 2,
/// <summary>Notify the caller of changes to a value of the key. This can
/// include adding or deleting a value, or changing an existing value.</summary>
Value = 4,
/// <summary>Notify the caller of changes to the security descriptor
/// of the key.</summary>
Security = 8,
}
}

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

@ -0,0 +1,109 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace BgTools.Utils
{
public class Styles
{
#region Colors
public class Colors {
public static Color DarkGray = new Color(0.09f, 0.09f, 0.09f);
public static Color LightGray = new Color(0.65f, 0.65f, 0.65f);
public static Color Red = new Color(1.00f, 0.00f, 0.00f);
public static Color Yellow = new Color(1.00f, 1.00f, 0.00f);
public static Color Blue = new Color(0.00f, 0.63f, 0.99f);
}
#endregion // Colors
#region Texture manager
static Dictionary<long, Texture2D> mTextures = new Dictionary<long, Texture2D>();
public static Texture2D GetTexture(long pColorRGBA)
{
if (mTextures.ContainsKey(pColorRGBA) && mTextures[pColorRGBA] != null)
return mTextures[pColorRGBA];
Color32 c = GetColor(pColorRGBA);
var texture = new Texture2D(4, 4);
for (int x = 0; x < 4; x++)
for (int y = 0; y < 4; y++)
texture.SetPixel(x, y, c);
texture.Apply();
texture.Compress(true);
mTextures[pColorRGBA] = texture;
return texture;
}
private static Color32 GetColor(long pColorRGBA)
{
byte r = (byte)((pColorRGBA & 0xff000000) >> 24);
byte g = (byte)((pColorRGBA & 0xff0000) >> 16);
byte b = (byte)((pColorRGBA & 0xff00) >> 8);
byte a = (byte)((pColorRGBA & 0xff));
Color32 c = new Color32(r, g, b, a);
return c;
}
#endregion Texture manager
static GUIStyle mHSeparator;
private static GUIStyle hSeparator
{
get
{
if (mHSeparator == null)
{
mHSeparator = new GUIStyle();
mHSeparator.alignment = TextAnchor.MiddleCenter;
mHSeparator.stretchWidth = true;
mHSeparator.fixedHeight = 1;
mHSeparator.margin = new RectOffset(20, 20, 5, 5);
mHSeparator.normal.background = (EditorGUIUtility.isProSkin) ? GetTexture(0xb5b5b5ff) : GetTexture(0x000000ff);
}
return mHSeparator;
}
}
public static void HorizontalSeparator()
{
GUILayout.Label("", hSeparator);
}
static GUIStyle Icon;
public static GUIStyle icon
{
get
{
if (Icon == null)
{
Icon = new GUIStyle();
Icon.fixedWidth = 15.0f;
Icon.fixedHeight = 15.0f;
Icon.margin = new RectOffset(2, 2, 2, 2);
}
return Icon;
}
}
static GUIStyle MiniButton;
public static GUIStyle miniButton
{
get
{
if (MiniButton == null)
{
MiniButton = new GUIStyle(GUI.skin.button);
MiniButton.fixedWidth = 15.0f;
MiniButton.fixedHeight = 15.0f;
MiniButton.margin = new RectOffset(2, 2, 2, 2);
MiniButton.padding = new RectOffset(2, 2, 2, 2);
}
return MiniButton;
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: be53f59c705f7434a9d6581d0746990f
timeCreated: 1496670894
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,16 @@
{
"name": "Unity.PlayerPrefsEditor.Editor",
"references": [
"Unity.PlayerPrefsEditor.EditorResources"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 516df2812c38a7348b10d202b71bf483
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,71 @@
# PlayerPrefs Editor for Unity 3D
[![Minimal unity editor version](https://img.shields.io/badge/UnityEditor-2019.4%20or%20later-blue.svg)](https://unity3d.com/de/get-unity/download/archive)
[![CI](https://github.com/Dysman/bgTools-playerPrefsEditor/workflows/CI/badge.svg)](https://github.com/Dysman/bgTools-playerPrefsEditor/actions)&nbsp;&nbsp;
[![Release](https://img.shields.io/github/v/release/Dysman/bgTools-playerPrefsEditor?include_prereleases&label=Release)](https://github.com/Dysman/bgTools-playerPrefsEditor/releases)
[![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/dysman/bgTools-playerPrefsEditor/upm?label=GitURL-UPM)](https://github.com/Dysman/bgTools-playerPrefsEditor/tree/upm)
[![openupm](https://img.shields.io/npm/v/com.bgtools.playerprefseditor?label=OpenUPM&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.bgtools.playerprefseditor)
[![AssetStore](https://img.shields.io/badge/dynamic/xml?url=http://u3d.as/1RLa&label=UnityAssetStore&query=//*[contains(@class,%20%27product-version%27)]/div[2]&prefix=v)](http://u3d.as/1RLa)
[![Flattr this git repo](https://img.shields.io/badge/_-Flattr-green?logo=flattr&style=flat)](https://flattr.com/@dysman)&nbsp;
[![Buy me a coffee](https://img.shields.io/badge/-Buy%20Me%20A%20Coffee-yellow?logo=BuyMeACoffee&style=flat&logoColor=white)](https://www.buymeacoffee.com/dysman)
[<img align="right" src="https://img.shields.io/discord/431522155814191116?logo=Discord&logoColor=white&style=flat&label=Discord&labelColor=5865F2">](https://discord.gg/8rcPZrD)
Tool extension for the Unity Editor that enables easy access to the player preferences over a simple UI. Allows to view, add, remove and modify entries on the development machine.
![Preference editor window](https://www.bgranzow.de/downloads/PlayerPrefsEditorV1_2_0.png)
## Features
* Add, remove and edit PlayerPrefs
* Intuitive visual editor
* Works with standard Unity PlayerPrefs
* Monitors changes from code
* Supports all editors (Windows, Linux, MacOS)
* Lightweight dockable for full integration in your workflow
* Supports both skins (Personal, Professional)
## Requirements
Unity Version: 2019.4 (LTS) or higher
Editor Version: Windows, MacOS, Linux
## Installation
The plugin provides *manual* and *UPM* installation.
Additionally it's available on the [Unity Asset Store](http://u3d.as/1RLa).
### Manual
Place the PlayerPrefsEditor folder somewhere in your project. It's not relevant where it's located, the plugin will find all of its files by itself.
### Unity Package Manager (UPM)
**Via Git URL**
Through the Unity Plugin Manager it's possible to install the plugin direct from this git repository.
The UPM need a specific structure what will be provided into the *upm* branch.
Use following direct URL for the configuration:
```
https://github.com/Dysman/bgTools-playerPrefsEditor.git#upm
```
See official Unity documentation for more informations: [UI](https://docs.unity3d.com/Manual/upm-ui-giturl.html) or [manifest.json](https://docs.unity3d.com/Manual/upm-git.html)
**Via OpenUPM**
The package is available on the [openupm registry](https://openupm.com). It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli).
```
openupm add com.bgtools.playerprefseditor
```
## Usage
The entry to open the _PlayerPrefs Editor_ is located in the top menu at Tools/BG Tools/PlayerPrefs Editor. It's a standard dockable window, so place it wherever it helps to be productive.
A more detailed manual can be fund in following locations:
* GitHub (Manual)- [Manual page](Packages/PlayerPrefsEditor/Documentation~/PlayerPrefsEditor.md)
* GitHub (UPM) - Press the _Documentation_ link on the UPM description.
* Unity Asset Store Package - [MANUAL.html](Documentation/MANUAL.html)

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

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

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

@ -0,0 +1,53 @@
using UnityEngine;
public class PlayerPrefsController : MonoBehaviour
{
#region Add
public void AddTestStrings()
{
PlayerPrefs.SetString("Runtime_String", "boing");
PlayerPrefs.SetString("Runtime_String2", "foo");
PlayerPrefs.Save();
}
public void AddTestInt()
{
PlayerPrefs.SetInt("Runtime_Int", 1234);
PlayerPrefs.Save();
}
public void AddTestFloat()
{
PlayerPrefs.SetFloat("Runtime_Float", 3.14f);
PlayerPrefs.Save();
}
#endregion
#region Remove
public void RemoveTestStrings()
{
PlayerPrefs.DeleteKey("Runtime_String");
PlayerPrefs.DeleteKey("Runtime_String2");
PlayerPrefs.Save();
}
public void RemoveTestInt()
{
PlayerPrefs.DeleteKey("Runtime_Int");
PlayerPrefs.Save();
}
public void RemoveTestFloat()
{
PlayerPrefs.DeleteKey("Runtime_Float");
PlayerPrefs.Save();
}
public void DeleteAll()
{
PlayerPrefs.DeleteAll();
PlayerPrefs.Save();
}
#endregion
}

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

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0a573fe589907b74fa928784d2c3aeca
guid: fba661fc32606eb498ae23fd271867b4
DefaultImporter:
externalObjects: {}
userData:

@ -0,0 +1,12 @@
{
"name": "Unity.PlayerPrefsEditor.Samples.SampleScene",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

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

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c1764d69117881843b761dc14ca276d4
folderAsset: yes
timeCreated: 1561225368
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 19f677a9eb83d3942af6d4c5fa8dbeee
folderAsset: yes
timeCreated: 1520032274
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,29 @@
{
"name": "AssetUsageDetector.Editor",
"rootNamespace": "",
"references": [
"Unity.Addressables"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.addressables",
"expression": "0.0.0",
"define": "ASSET_USAGE_ADDRESSABLES"
},
{
"name": "com.unity.visualeffectgraph",
"expression": "0.0.0",
"define": "ASSET_USAGE_VFX_GRAPH"
}
],
"noEngineReferences": false
}

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8579ab42c9ab63d4bac5fb07bd390b46
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2c0dea52dcdb16e4e9b13f8dacc1590f
timeCreated: 1520032279
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace AssetUsageDetectorNamespace
{
public partial class AssetUsageDetector
{
#region Helper Classes
private class CacheEntry
{
public enum Result { Unknown = 0, No = 1, Yes = 2 };
public string hash;
public string[] dependencies;
public long[] fileSizes;
public bool verified;
public Result searchResult;
public CacheEntry( string path )
{
Verify( path );
}
public CacheEntry( string hash, string[] dependencies, long[] fileSizes )
{
this.hash = hash;
this.dependencies = dependencies;
this.fileSizes = fileSizes;
}
public void Verify( string path )
{
string hash = AssetDatabase.GetAssetDependencyHash( path ).ToString();
if( this.hash != hash )
{
this.hash = hash;
Refresh( path );
}
verified = true;
}
public void Refresh( string path )
{
dependencies = AssetDatabase.GetDependencies( path, false );
if( fileSizes == null || fileSizes.Length != dependencies.Length )
fileSizes = new long[dependencies.Length];
int length = dependencies.Length;
for( int i = 0; i < length; i++ )
{
if( !string.IsNullOrEmpty( dependencies[i] ) )
{
FileInfo assetFile = new FileInfo( dependencies[i] );
fileSizes[i] = assetFile.Exists ? assetFile.Length : 0L;
}
else
{
// This dependency is empty which causes issues when passed to FileInfo constructor
// Find a non-empty dependency and move it to this index
for( int j = length - 1; j > i; j--, length-- )
{
if( !string.IsNullOrEmpty( dependencies[j] ) )
{
dependencies[i--] = dependencies[j];
break;
}
}
length--;
}
}
if( length != fileSizes.Length )
{
Array.Resize( ref dependencies, length );
Array.Resize( ref fileSizes, length );
}
}
}
#endregion
// An optimization to fetch the dependencies of an asset only once (key is the path of the asset)
private Dictionary<string, CacheEntry> assetDependencyCache;
private CacheEntry lastRefreshedCacheEntry;
private string CachePath { get { return Application.dataPath + "/../Library/AssetUsageDetector.cache"; } } // Path of the cache file
public void SaveCache()
{
if( assetDependencyCache == null )
return;
try
{
using( FileStream stream = new FileStream( CachePath, FileMode.Create ) )
using( BinaryWriter writer = new BinaryWriter( stream ) )
{
writer.Write( assetDependencyCache.Count );
foreach( var keyValuePair in assetDependencyCache )
{
CacheEntry cacheEntry = keyValuePair.Value;
string[] dependencies = cacheEntry.dependencies;
long[] fileSizes = cacheEntry.fileSizes;
writer.Write( keyValuePair.Key );
writer.Write( cacheEntry.hash );
writer.Write( dependencies.Length );
for( int i = 0; i < dependencies.Length; i++ )
{
writer.Write( dependencies[i] );
writer.Write( fileSizes[i] );
}
}
}
}
catch( Exception e )
{
Debug.LogException( e );
}
}
private void LoadCache()
{
if( File.Exists( CachePath ) )
{
using( FileStream stream = new FileStream( CachePath, FileMode.Open, FileAccess.Read ) )
using( BinaryReader reader = new BinaryReader( stream ) )
{
try
{
int cacheSize = reader.ReadInt32();
assetDependencyCache = new Dictionary<string, CacheEntry>( cacheSize );
for( int i = 0; i < cacheSize; i++ )
{
string assetPath = reader.ReadString();
string hash = reader.ReadString();
int dependenciesLength = reader.ReadInt32();
string[] dependencies = new string[dependenciesLength];
long[] fileSizes = new long[dependenciesLength];
for( int j = 0; j < dependenciesLength; j++ )
{
dependencies[j] = reader.ReadString();
fileSizes[j] = reader.ReadInt64();
}
assetDependencyCache[assetPath] = new CacheEntry( hash, dependencies, fileSizes );
}
}
catch( Exception e )
{
assetDependencyCache = null;
Debug.LogWarning( "Couldn't load cache (probably cache format has changed in an update), will regenerate cache.\n" + e.ToString() );
}
}
}
// Generate cache for all assets for the first time
if( assetDependencyCache == null )
{
assetDependencyCache = new Dictionary<string, CacheEntry>( 1024 * 8 );
string[] allAssets = AssetDatabase.GetAllAssetPaths();
if( allAssets.Length > 0 )
{
double startTime = EditorApplication.timeSinceStartup;
try
{
for( int i = 0; i < allAssets.Length; i++ )
{
if( i % 30 == 0 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Generating cache for the first time (optional)", (float) i / allAssets.Length ) )
{
EditorUtility.ClearProgressBar();
Debug.LogWarning( "Initial cache generation cancelled, cache will be generated on the fly as more and more assets are searched." );
break;
}
assetDependencyCache[allAssets[i]] = new CacheEntry( allAssets[i] );
}
EditorUtility.ClearProgressBar();
Debug.Log( "Cache generated in " + ( EditorApplication.timeSinceStartup - startTime ).ToString( "F2" ) + " seconds" );
Debug.Log( "You can always reset the cache by deleting " + Path.GetFullPath( CachePath ) );
SaveCache();
}
catch( Exception e )
{
EditorUtility.ClearProgressBar();
Debug.LogException( e );
}
}
}
}
}
}

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

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

@ -0,0 +1,298 @@
using UnityEditor;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public static class AssetUsageDetectorSettings
{
private static readonly GUILayoutOption GL_WIDTH_60 = GUILayout.Width( 60f );
#region Colors
private static Color? m_settingsHeaderColor = null;
public static Color SettingsHeaderColor
{
get { if( m_settingsHeaderColor == null ) m_settingsHeaderColor = GetColor( "AUD_SettingsHeaderTint", Color.cyan ); return m_settingsHeaderColor.Value; }
set { if( m_settingsHeaderColor == value ) return; m_settingsHeaderColor = value; SetColor( "AUD_SettingsHeaderTint", value ); }
}
private static Color? m_searchResultGroupHeaderColor = null;
public static Color SearchResultGroupHeaderColor
{
get { if( m_searchResultGroupHeaderColor == null ) m_searchResultGroupHeaderColor = GetColor( "AUD_ResultGroupHeaderTint", Color.cyan ); return m_searchResultGroupHeaderColor.Value; }
set { if( m_searchResultGroupHeaderColor == value ) return; m_searchResultGroupHeaderColor = value; SetColor( "AUD_ResultGroupHeaderTint", value ); }
}
private static Color? m_rootRowsBackgroundColor = null;
public static Color RootRowsBackgroundColor
{
get { if( m_rootRowsBackgroundColor == null ) m_rootRowsBackgroundColor = GetColor( "AUD_RootRowsTint", EditorGUIUtility.isProSkin ? new Color( 0f, 1f, 1f, 0.15f ) : new Color( 0f, 1f, 1f, 0.25f ) ); return m_rootRowsBackgroundColor.Value; }
set { if( m_rootRowsBackgroundColor == value ) return; m_rootRowsBackgroundColor = value; SetColor( "AUD_RootRowsTint", value ); }
}
private static Color? m_rootRowsBorderColor = null;
public static Color RootRowsBorderColor
{
get { if( m_rootRowsBorderColor == null ) m_rootRowsBorderColor = GetColor( "AUD_RootRowsBorderColor", EditorGUIUtility.isProSkin ? new Color( 0.15f, 0.15f, 0.15f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); return m_rootRowsBorderColor.Value; }
set { if( m_rootRowsBorderColor == value ) return; m_rootRowsBorderColor = value; SetColor( "AUD_RootRowsBorderColor", value ); }
}
private static Color? m_mainReferencesBackgroundColor = null;
public static Color MainReferencesBackgroundColor
{
get { if( m_mainReferencesBackgroundColor == null ) m_mainReferencesBackgroundColor = GetColor( "AUD_MainRefRowsTint", EditorGUIUtility.isProSkin ? new Color( 0f, 0.35f, 0f, 1f ) : new Color( 0.25f, 0.75f, 0.25f, 1f ) ); return m_mainReferencesBackgroundColor.Value; }
set { if( m_mainReferencesBackgroundColor == value ) return; m_mainReferencesBackgroundColor = value; SetColor( "AUD_MainRefRowsTint", value ); }
}
private static Color? m_selectedRowsParentTint = null;
public static Color SelectedRowParentsTint
{
get { if( m_selectedRowsParentTint == null ) m_selectedRowsParentTint = GetColor( "AUD_SelectedRowParentsTint", EditorGUIUtility.isProSkin ? new Color( 0.36f, 0.36f, 0.18f, 1f ) : new Color( 0.825f, 0.825f, 0.55f, 1f ) ); return m_selectedRowsParentTint.Value; }
set { if( m_selectedRowsParentTint == value ) return; m_selectedRowsParentTint = value; SetColor( "AUD_SelectedRowParentsTint", value ); }
}
private static Color? m_selectedRowOccurrencesColor = null;
public static Color SelectedRowOccurrencesColor
{
get { if( m_selectedRowOccurrencesColor == null ) m_selectedRowOccurrencesColor = GetColor( "AUD_SelectedRowOccurrencesTint", EditorGUIUtility.isProSkin ? new Color( 0f, 0.3f, 0.75f, 1f ) : new Color( 0.25f, 0.75f, 1f, 1f ) ); return m_selectedRowOccurrencesColor.Value; }
set { if( m_selectedRowOccurrencesColor == value ) return; m_selectedRowOccurrencesColor = value; SetColor( "AUD_SelectedRowOccurrencesTint", value ); }
}
private static Color? m_treeLinesColor = null;
public static Color TreeLinesColor
{
get { if( m_treeLinesColor == null ) m_treeLinesColor = GetColor( "AUD_TreeLinesColor", EditorGUIUtility.isProSkin ? new Color( 0.65f, 0.65f, 0.65f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); return m_treeLinesColor.Value; }
set { if( m_treeLinesColor == value ) return; m_treeLinesColor = value; SetColor( "AUD_TreeLinesColor", value ); }
}
private static Color? m_highlightedTreeLinesColor = null;
public static Color HighlightedTreeLinesColor
{
get { if( m_highlightedTreeLinesColor == null ) m_highlightedTreeLinesColor = GetColor( "AUD_HighlightTreeLinesColor", Color.cyan ); return m_highlightedTreeLinesColor.Value; }
set { if( m_highlightedTreeLinesColor == value ) return; m_highlightedTreeLinesColor = value; SetColor( "AUD_HighlightTreeLinesColor", value ); }
}
private static Color? m_searchMatchingTextColor = null;
public static Color SearchMatchingTextColor
{
get { if( m_searchMatchingTextColor == null ) m_searchMatchingTextColor = GetColor( "AUD_SearchTextColor", Color.red ); return m_searchMatchingTextColor.Value; }
set { if( m_searchMatchingTextColor == value ) return; m_searchMatchingTextColor = value; SetColor( "AUD_SearchTextColor", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged( highlightedSearchTextColorChanged: true ) ); }
}
private static Color? m_tooltipDescriptionTextColor = null;
public static Color TooltipDescriptionTextColor
{
get { if( m_tooltipDescriptionTextColor == null ) m_tooltipDescriptionTextColor = GetColor( "AUD_TooltipUsageTextColor", EditorGUIUtility.isProSkin ? new Color( 0f, 0.9f, 0.9f, 1f ) : new Color( 0.9f, 0f, 0f, 1f ) ); return m_tooltipDescriptionTextColor.Value; }
set { if( m_tooltipDescriptionTextColor == value ) return; m_tooltipDescriptionTextColor = value; SetColor( "AUD_TooltipUsageTextColor", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged( tooltipDescriptionsColorChanged: true ) ); }
}
#endregion
#region Size Adjustments
private static float? m_extraRowHeight = null;
public static float ExtraRowHeight
{
get { if( m_extraRowHeight == null ) m_extraRowHeight = EditorPrefs.GetFloat( "AUD_ExtraRowHeight", 0f ); return m_extraRowHeight.Value; }
set { if( m_extraRowHeight == value ) return; m_extraRowHeight = value; EditorPrefs.SetFloat( "AUD_ExtraRowHeight", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged() ); }
}
#endregion
#region Other Settings
private static bool? m_showRootAssetName = null;
public static bool ShowRootAssetName
{
get { if( m_showRootAssetName == null ) m_showRootAssetName = EditorPrefs.GetBool( "AUD_ShowRootAssetName", true ); return m_showRootAssetName.Value; }
set { if( m_showRootAssetName == value ) return; m_showRootAssetName = value; EditorPrefs.SetBool( "AUD_ShowRootAssetName", value ); }
}
private static bool? m_pingClickedObjects = null;
public static bool PingClickedObjects
{
get { if( m_pingClickedObjects == null ) m_pingClickedObjects = EditorPrefs.GetBool( "AUD_PingClickedObj", true ); return m_pingClickedObjects.Value; }
set { if( m_pingClickedObjects == value ) return; m_pingClickedObjects = value; EditorPrefs.SetBool( "AUD_PingClickedObj", value ); }
}
private static bool? m_selectClickedObjects = null;
public static bool SelectClickedObjects
{
get { if( m_selectClickedObjects == null ) m_selectClickedObjects = EditorPrefs.GetBool( "AUD_SelectClickedObj", false ); return m_selectClickedObjects.Value; }
set { if( m_selectClickedObjects == value ) return; m_selectClickedObjects = value; EditorPrefs.SetBool( "AUD_SelectClickedObj", value ); }
}
private static bool? m_selectDoubleClickedObjects = null;
public static bool SelectDoubleClickedObjects
{
get { if( m_selectDoubleClickedObjects == null ) m_selectDoubleClickedObjects = EditorPrefs.GetBool( "AUD_SelectDoubleClickedObj", true ); return m_selectDoubleClickedObjects.Value; }
set { if( m_selectDoubleClickedObjects == value ) return; m_selectDoubleClickedObjects = value; EditorPrefs.SetBool( "AUD_SelectDoubleClickedObj", value ); }
}
private static bool? m_markUsedAssetsSubAssetsAsUsed = null;
public static bool MarkUsedAssetsSubAssetsAsUsed
{
get { if( m_markUsedAssetsSubAssetsAsUsed == null ) m_markUsedAssetsSubAssetsAsUsed = EditorPrefs.GetBool( "AUD_MarkUsedAssetsSubAssetsAsUsed", true ); return m_markUsedAssetsSubAssetsAsUsed.Value; }
set { if( m_markUsedAssetsSubAssetsAsUsed == value ) return; m_markUsedAssetsSubAssetsAsUsed = value; EditorPrefs.SetBool( "AUD_MarkUsedAssetsSubAssetsAsUsed", value ); }
}
private static bool? m_showUnityTooltip = null;
public static bool ShowUnityTooltip
{
get { if( m_showUnityTooltip == null ) m_showUnityTooltip = EditorPrefs.GetBool( "AUD_ShowUnityTooltip", false ); return m_showUnityTooltip.Value; }
set { if( m_showUnityTooltip == value ) return; m_showUnityTooltip = value; EditorPrefs.SetBool( "AUD_ShowUnityTooltip", value ); }
}
private static bool? m_showCustomTooltip = null;
public static bool ShowCustomTooltip
{
get { if( m_showCustomTooltip == null ) m_showCustomTooltip = EditorPrefs.GetBool( "AUD_ShowCustomTooltip", true ); return m_showCustomTooltip.Value; }
set { if( m_showCustomTooltip == value ) return; m_showCustomTooltip = value; EditorPrefs.SetBool( "AUD_ShowCustomTooltip", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged() ); }
}
private static float? m_customTooltipDelay = null;
public static float CustomTooltipDelay
{
get { if( m_customTooltipDelay == null ) m_customTooltipDelay = EditorPrefs.GetFloat( "AUD_CustomTooltipDelay", 0.7f ); return m_customTooltipDelay.Value; }
set { if( m_customTooltipDelay == value ) return; m_customTooltipDelay = value; EditorPrefs.SetFloat( "AUD_CustomTooltipDelay", value ); }
}
private static bool? m_showTreeLines = null;
public static bool ShowTreeLines
{
get { if( m_showTreeLines == null ) m_showTreeLines = EditorPrefs.GetBool( "AUD_ShowTreeLines", true ); return m_showTreeLines.Value; }
set { if( m_showTreeLines == value ) return; m_showTreeLines = value; EditorPrefs.SetBool( "AUD_ShowTreeLines", value ); }
}
private static bool? m_applySelectedRowParentsTintToRootRows = null;
public static bool ApplySelectedRowParentsTintToRootRows
{
get { if( m_applySelectedRowParentsTintToRootRows == null ) m_applySelectedRowParentsTintToRootRows = EditorPrefs.GetBool( "AUD_SelectedRowParentsTintAtRoot", true ); return m_applySelectedRowParentsTintToRootRows.Value; }
set { if( m_applySelectedRowParentsTintToRootRows == value ) return; m_applySelectedRowParentsTintToRootRows = value; EditorPrefs.SetBool( "AUD_SelectedRowParentsTintAtRoot", value ); }
}
#endregion
#if UNITY_2018_3_OR_NEWER
[SettingsProvider]
public static SettingsProvider CreatePreferencesGUI()
{
return new SettingsProvider( "Project/yasirkula/Asset Usage Detector", SettingsScope.Project )
{
guiHandler = ( searchContext ) => PreferencesGUI(),
keywords = new System.Collections.Generic.HashSet<string>() { "Asset", "Usage", "Detector" }
};
}
#endif
#if !UNITY_2018_3_OR_NEWER
[PreferenceItem( "Asset Usage Detector" )]
#endif
public static void PreferencesGUI()
{
float labelWidth = EditorGUIUtility.labelWidth;
#if UNITY_2018_3_OR_NEWER
EditorGUIUtility.labelWidth += 60f;
#else
EditorGUIUtility.labelWidth += 20f;
#endif
EditorGUI.BeginChangeCheck();
ShowRootAssetName = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Root Asset's Name For Sub-Assets (Requires Refresh)", ShowRootAssetName );
EditorGUILayout.Space();
PingClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Ping Clicked Objects", PingClickedObjects );
SelectClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Select Clicked Objects", SelectClickedObjects );
SelectDoubleClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Select Double Clicked Objects", SelectDoubleClickedObjects );
EditorGUILayout.Space();
MarkUsedAssetsSubAssetsAsUsed = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Hide unused sub-assets in \"Unused Objects\" list if their parent assets are used (Requires Refresh)", MarkUsedAssetsSubAssetsAsUsed );
EditorGUILayout.Space();
ShowUnityTooltip = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Unity Tooltip", ShowUnityTooltip );
ShowCustomTooltip = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Custom Tooltip", ShowCustomTooltip );
EditorGUI.indentLevel++;
CustomTooltipDelay = FloatField( "Delay", CustomTooltipDelay, 0.7f );
EditorGUI.indentLevel--;
TooltipDescriptionTextColor = ColorField( "Tooltip Descriptions Text Color", TooltipDescriptionTextColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.9f, 0.9f, 1f ) : new Color( 0.9f, 0f, 0f, 1f ) );
EditorGUILayout.Space();
ExtraRowHeight = Mathf.Max( 0f, FloatField( "Extra Row Height", ExtraRowHeight, 0f ) );
EditorGUILayout.Space();
SettingsHeaderColor = ColorField( "Settings Header Color", SettingsHeaderColor, Color.cyan );
SearchResultGroupHeaderColor = ColorField( "Group Header Color", SearchResultGroupHeaderColor, Color.cyan );
RootRowsBackgroundColor = ColorField( "Root Rows Background Color", RootRowsBackgroundColor, EditorGUIUtility.isProSkin ? new Color( 0f, 1f, 1f, 0.15f ) : new Color( 0f, 1f, 1f, 0.25f ) );
RootRowsBorderColor = ColorField( "Root Rows Border Color", RootRowsBorderColor, EditorGUIUtility.isProSkin ? new Color( 0.15f, 0.15f, 0.15f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) );
MainReferencesBackgroundColor = ColorField( "Main References Background Color", MainReferencesBackgroundColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.35f, 0f, 1f ) : new Color( 0.25f, 0.75f, 0.25f, 1f ) );
SelectedRowParentsTint = ColorField( "Selected Row Parents Tint", SelectedRowParentsTint, EditorGUIUtility.isProSkin ? new Color( 0.36f, 0.36f, 0.18f, 1f ) : new Color( 0.825f, 0.825f, 0.55f, 1f ) );
EditorGUI.indentLevel++;
ApplySelectedRowParentsTintToRootRows = !EditorGUILayout.Toggle( "Ignore Root Rows", !ApplySelectedRowParentsTintToRootRows );
EditorGUI.indentLevel--;
SelectedRowOccurrencesColor = ColorField( "Selected Row All Occurrences Tint", SelectedRowOccurrencesColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.3f, 0.75f, 1f ) : new Color( 0.25f, 0.75f, 1f, 1f ) );
SearchMatchingTextColor = ColorField( "Matching Search Text Color", SearchMatchingTextColor, Color.red );
ShowTreeLines = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Tree Lines", ShowTreeLines );
EditorGUI.indentLevel++;
TreeLinesColor = ColorField( "Normal Color", TreeLinesColor, EditorGUIUtility.isProSkin ? new Color( 0.65f, 0.65f, 0.65f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) );
HighlightedTreeLinesColor = ColorField( "Highlighted Color", HighlightedTreeLinesColor, Color.cyan );
EditorGUI.indentLevel--;
EditorGUIUtility.labelWidth = labelWidth;
if( EditorGUI.EndChangeCheck() )
ForEachAssetUsageDetectorWindow( ( window ) => window.Repaint() );
}
private static Color ColorField( string label, Color value, Color defaultValue )
{
GUILayout.BeginHorizontal();
Color result = EditorGUILayout.ColorField( label, value );
if( GUILayout.Button( "Reset", GL_WIDTH_60 ) )
result = defaultValue;
GUILayout.EndHorizontal();
return result;
}
private static float FloatField( string label, float value, float defaultValue )
{
GUILayout.BeginHorizontal();
float result = EditorGUILayout.FloatField( label, value );
if( GUILayout.Button( "Reset", GL_WIDTH_60 ) )
result = defaultValue;
GUILayout.EndHorizontal();
return result;
}
private static Color GetColor( string pref, Color defaultColor )
{
if( EditorGUIUtility.isProSkin )
pref += "_Pro";
if( !EditorPrefs.HasKey( pref ) )
return defaultColor;
string[] parts = EditorPrefs.GetString( pref ).Split( ';' );
return new Color32( byte.Parse( parts[0] ), byte.Parse( parts[1] ), byte.Parse( parts[2] ), byte.Parse( parts[3] ) );
}
private static void SetColor( string pref, Color32 value )
{
if( EditorGUIUtility.isProSkin )
pref += "_Pro";
EditorPrefs.SetString( pref, string.Concat( value.r.ToString(), ";", value.g.ToString(), ";", value.b.ToString(), ";", value.a.ToString() ) );
}
private static void ForEachAssetUsageDetectorWindow( System.Action<AssetUsageDetectorWindow> action )
{
foreach( AssetUsageDetectorWindow window in Resources.FindObjectsOfTypeAll<AssetUsageDetectorWindow>() )
{
if( window )
action( window );
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 13295073724765e45aa3b77486e515f4
timeCreated: 1639982865
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,812 @@
// Asset Usage Detector - by Suleyman Yasir KULA (yasirkula@gmail.com)
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
using Object = UnityEngine.Object;
#if UNITY_2021_2_OR_NEWER
using PrefabStage = UnityEditor.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility;
#elif UNITY_2018_3_OR_NEWER
using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility;
#endif
namespace AssetUsageDetectorNamespace
{
public enum Phase { Setup, Processing, Complete };
public class AssetUsageDetectorWindow : EditorWindow, IHasCustomMenu
{
private enum WindowFilter { AlwaysReturnActive, ReturnActiveIfNotLocked, AlwaysReturnNew };
private const string PREFS_SEARCH_SCENES = "AUD_SceneSearch";
private const string PREFS_SEARCH_SCENE_LIGHTING_SETTINGS = "AUD_LightingSettingsSearch";
private const string PREFS_SEARCH_ASSETS = "AUD_AssetsSearch";
private const string PREFS_SEARCH_PROJECT_SETTINGS = "AUD_ProjectSettingsSearch";
private const string PREFS_DONT_SEARCH_SOURCE_ASSETS = "AUD_AssetsExcludeSrc";
private const string PREFS_SEARCH_DEPTH_LIMIT = "AUD_Depth";
private const string PREFS_SEARCH_FIELDS = "AUD_Fields";
private const string PREFS_SEARCH_PROPERTIES = "AUD_Properties";
private const string PREFS_SEARCH_NON_SERIALIZABLES = "AUD_NonSerializables";
private const string PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES = "AUD_SearchUnusedMaterialProps";
private const string PREFS_LAZY_SCENE_SEARCH = "AUD_LazySceneSearch";
private const string PREFS_ADDRESSABLES_SUPPORT = "AUD_AddressablesSupport";
private const string PREFS_CALCULATE_UNUSED_OBJECTS = "AUD_FindUnusedObjs";
private const string PREFS_HIDE_DUPLICATE_ROWS = "AUD_HideDuplicates";
private const string PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS = "AUD_HideRedundantPVariantLinks";
private const string PREFS_SHOW_PROGRESS = "AUD_Progress";
private static readonly GUIContent windowTitle = new GUIContent( "Asset Usage Detector" );
private static readonly Vector2 windowMinSize = new Vector2( 325f, 220f );
private static readonly GUILayoutOption GL_WIDTH_12 = GUILayout.Width( 12f );
private GUIStyle lockButtonStyle;
private readonly AssetUsageDetector core = new AssetUsageDetector();
private SearchResult searchResult; // Overall search results
// This isn't readonly so that it can be serialized
private List<ObjectToSearch> objectsToSearch = new List<ObjectToSearch>() { new ObjectToSearch( null ) };
#pragma warning disable 0649
[SerializeField] // Since titleContent persists between Editor sessions, so should the IsLocked property because otherwise, "[L]" in title becomes confusing when the EditorWindow isn't actually locked
private bool m_isLocked;
private bool IsLocked
{
get { return m_isLocked; }
set
{
if( m_isLocked != value )
{
m_isLocked = value;
titleContent = value ? new GUIContent( "[L] " + windowTitle.text, EditorGUIUtility.IconContent( "InspectorLock" ).image ) : windowTitle;
}
}
}
#pragma warning restore 0649
private Phase currentPhase = Phase.Setup;
private bool searchInOpenScenes = true; // Scenes currently open in Hierarchy view
private bool searchInScenesInBuild = true; // Scenes in build
private bool searchInScenesInBuildTickedOnly = true; // Scenes in build (ticked only or not)
private bool searchInAllScenes = true; // All scenes (including scenes that are not in build)
private bool searchInSceneLightingSettings = true; // Window-Rendering-Lighting settings
private bool searchInAssetsFolder = true; // Assets in Project window
private bool dontSearchInSourceAssets = true; // objectsToSearch won't be searched for internal references
private bool searchInProjectSettings = true; // Player Settings, Graphics Settings etc.
private List<Object> searchInAssetsSubset = new List<Object>() { null }; // If not empty, only these assets are searched for references
private List<Object> excludedAssets = new List<Object>() { null }; // These assets won't be searched for references
private List<Object> excludedScenes = new List<Object>() { null }; // These scenes won't be searched for references
private int searchDepthLimit = 4; // Depth limit for recursively searching variables of objects
private bool lazySceneSearch = true;
#if ASSET_USAGE_ADDRESSABLES
private bool addressablesSupport = false;
#endif
private bool searchNonSerializableVariables = true;
private bool searchUnusedMaterialProperties = true;
private bool calculateUnusedObjects = false;
private bool hideDuplicateRows = true;
private bool hideReduntantPrefabVariantLinks = true;
private bool noAssetDatabaseChanges = false;
private bool showDetailedProgressBar = true;
private BindingFlags fieldModifiers, propertyModifiers;
private SearchRefactoring searchRefactoring = null; // Its value can be assigned via ShowAndSearch
private readonly ObjectToSearchListDrawer objectsToSearchDrawer = new ObjectToSearchListDrawer();
private readonly ObjectListDrawer searchInAssetsSubsetDrawer = new ObjectListDrawer( "Search following asset(s) only:", false );
private readonly ObjectListDrawer excludedAssetsDrawer = new ObjectListDrawer( "Don't search following asset(s):", false );
private readonly ObjectListDrawer excludedScenesDrawer = new ObjectListDrawer( "Don't search in following scene(s):", false );
private bool drawObjectsToSearchSection = true;
private Vector2 scrollPosition = Vector2.zero;
private bool shouldRepositionSelf;
private Rect windowTargetPosition;
void IHasCustomMenu.AddItemsToMenu( GenericMenu contextMenu )
{
contextMenu.AddItem( new GUIContent( "Lock" ), IsLocked, () => IsLocked = !IsLocked );
contextMenu.AddSeparator( "" );
#if UNITY_2018_3_OR_NEWER
contextMenu.AddItem( new GUIContent( "Settings" ), false, () => SettingsService.OpenProjectSettings( "Project/yasirkula/Asset Usage Detector" ) );
#else
contextMenu.AddItem( new GUIContent( "Settings" ), false, () =>
{
System.Type preferencesWindowType = typeof( EditorWindow ).Assembly.GetType( "UnityEditor.PreferencesWindow" );
preferencesWindowType.GetMethod( "ShowPreferencesWindow", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).Invoke( null, null );
EditorWindow preferencesWindow = GetWindow( preferencesWindowType );
if( (bool) preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow ) )
{
preferencesWindowType.GetMethod( "AddCustomSections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( preferencesWindow, null );
preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, false );
}
int targetSectionIndex = -1;
System.Collections.IList sections = (System.Collections.IList) preferencesWindowType.GetField( "m_Sections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow );
for( int i = 0; i < sections.Count; i++ )
{
if( ( (GUIContent) sections[i].GetType().GetField( "content", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( sections[i] ) ).text == "Asset Usage Detector" )
{
targetSectionIndex = i;
break;
}
}
if( targetSectionIndex >= 0 )
preferencesWindowType.GetProperty( "selectedSectionIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, targetSectionIndex, null );
} );
#endif
if( currentPhase == Phase.Setup )
{
contextMenu.AddSeparator( "" );
contextMenu.AddItem( new GUIContent( "Refresh Sub-Assets of Searched Objects" ), false, () =>
{
for( int i = objectsToSearch.Count - 1; i >= 0; i-- )
objectsToSearch[i].RefreshSubAssets();
} );
}
else if( currentPhase == Phase.Complete )
{
if( searchResult != null && searchResult.NumberOfGroups > 0 )
{
contextMenu.AddSeparator( "" );
contextMenu.AddItem( new GUIContent( "Collapse All" ), false, searchResult.CollapseAllSearchResultGroups );
}
}
}
// Shows lock button at the top-right corner
// Credit: http://leahayes.co.uk/2013/04/30/adding-the-little-padlock-button-to-your-editorwindow.html
private void ShowButton( Rect position )
{
if( lockButtonStyle == null )
lockButtonStyle = "IN LockButton";
IsLocked = GUI.Toggle( position, IsLocked, GUIContent.none, lockButtonStyle );
}
private static AssetUsageDetectorWindow GetWindow( WindowFilter filter )
{
AssetUsageDetectorWindow[] windows = Resources.FindObjectsOfTypeAll<AssetUsageDetectorWindow>();
AssetUsageDetectorWindow window = System.Array.Find( windows, ( w ) => w && !w.IsLocked );
if( !window )
window = System.Array.Find( windows, ( w ) => w );
if( window && ( filter == WindowFilter.AlwaysReturnActive || ( !window.IsLocked && filter == WindowFilter.ReturnActiveIfNotLocked ) ) )
{
window.Show();
window.Focus();
return window;
}
Rect? windowTargetPosition = null;
if( window )
{
Rect position = window.position;
position.position += new Vector2( 50f, 50f );
windowTargetPosition = position;
}
window = CreateInstance<AssetUsageDetectorWindow>();
window.titleContent = windowTitle;
window.minSize = windowMinSize;
if( windowTargetPosition.HasValue )
{
window.shouldRepositionSelf = true;
window.windowTargetPosition = windowTargetPosition.Value;
}
window.Show( true );
window.Focus();
return window;
}
[MenuItem( "Window/Asset Usage Detector/Active Window" )]
private static void OpenActiveWindow()
{
GetWindow( WindowFilter.AlwaysReturnActive );
}
[MenuItem( "Window/Asset Usage Detector/New Window" )]
private static void OpenNewWindow()
{
GetWindow( WindowFilter.AlwaysReturnNew );
}
// Quickly initiate search for the selected assets
[MenuItem( "GameObject/Search for References/This Object Only", priority = 49 )]
[MenuItem( "Assets/Search for References", priority = 1000 )]
private static void SearchSelectedAssetReferences( MenuCommand command )
{
// This happens when this button is clicked via hierarchy's right click context menu
// and is called once for each object in the selection. We don't want that, we want
// the function to be called only once
if( command.context )
{
EditorApplication.update -= CallSearchSelectedAssetReferencesOnce;
EditorApplication.update += CallSearchSelectedAssetReferencesOnce;
}
else
ShowAndSearch( Selection.objects );
}
[MenuItem( "GameObject/Search for References/Include Children", priority = 49 )]
private static void SearchSelectedAssetReferencesWithChildren( MenuCommand command )
{
if( command.context )
{
EditorApplication.update -= CallSearchSelectedAssetReferencesWithChildrenOnce;
EditorApplication.update += CallSearchSelectedAssetReferencesWithChildrenOnce;
}
else
ShowAndSearch( Selection.objects, true );
}
// Show the menu item only if there is a selection in the Editor
[MenuItem( "GameObject/Search for References/This Object Only", validate = true )]
[MenuItem( "GameObject/Search for References/Include Children", validate = true )]
[MenuItem( "Assets/Search for References", validate = true )]
private static bool SearchSelectedAssetReferencesValidate( MenuCommand command )
{
return Selection.objects.Length > 0;
}
// Quickly show the AssetUsageDetector window and initiate a search
public static void ShowAndSearch( IEnumerable<Object> searchObjects, bool? shouldSearchChildren = null )
{
GetWindow( WindowFilter.ReturnActiveIfNotLocked ).ShowAndSearchInternal( searchObjects, null, shouldSearchChildren );
}
// Quickly show the AssetUsageDetector window and initiate a search
public static void ShowAndSearch( AssetUsageDetector.Parameters searchParameters, bool? shouldSearchChildren = null )
{
if( searchParameters == null )
{
Debug.LogError( "searchParameters can't be null!" );
return;
}
GetWindow( WindowFilter.ReturnActiveIfNotLocked ).ShowAndSearchInternal( searchParameters.objectsToSearch, searchParameters, shouldSearchChildren );
}
private static void CallSearchSelectedAssetReferencesOnce()
{
EditorApplication.update -= CallSearchSelectedAssetReferencesOnce;
SearchSelectedAssetReferences( new MenuCommand( null ) );
}
private static void CallSearchSelectedAssetReferencesWithChildrenOnce()
{
EditorApplication.update -= CallSearchSelectedAssetReferencesWithChildrenOnce;
SearchSelectedAssetReferencesWithChildren( new MenuCommand( null ) );
}
private void ShowAndSearchInternal( IEnumerable<Object> searchObjects, AssetUsageDetector.Parameters searchParameters, bool? shouldSearchChildren )
{
if( !ReturnToSetupPhase() )
{
Debug.LogError( "Need to reset the previous search first!" );
return;
}
objectsToSearch.Clear();
if( searchObjects != null )
{
foreach( Object obj in searchObjects )
objectsToSearch.Add( new ObjectToSearch( obj, shouldSearchChildren ) );
}
if( searchParameters != null )
{
ParseSceneSearchMode( searchParameters.searchInScenes );
searchInSceneLightingSettings = searchParameters.searchInSceneLightingSettings;
searchInAssetsFolder = searchParameters.searchInAssetsFolder;
dontSearchInSourceAssets = searchParameters.dontSearchInSourceAssets;
searchInProjectSettings = searchParameters.searchInProjectSettings;
searchDepthLimit = searchParameters.searchDepthLimit;
fieldModifiers = searchParameters.fieldModifiers;
propertyModifiers = searchParameters.propertyModifiers;
searchNonSerializableVariables = searchParameters.searchNonSerializableVariables;
searchUnusedMaterialProperties = searchParameters.searchUnusedMaterialProperties;
searchRefactoring = searchParameters.searchRefactoring;
lazySceneSearch = searchParameters.lazySceneSearch;
#if ASSET_USAGE_ADDRESSABLES
addressablesSupport = searchParameters.addressablesSupport;
#endif
calculateUnusedObjects = searchParameters.calculateUnusedObjects;
hideDuplicateRows = searchParameters.hideDuplicateRows;
hideReduntantPrefabVariantLinks = searchParameters.hideReduntantPrefabVariantLinks;
noAssetDatabaseChanges = searchParameters.noAssetDatabaseChanges;
showDetailedProgressBar = searchParameters.showDetailedProgressBar;
searchInAssetsSubset.Clear();
if( searchParameters.searchInAssetsSubset != null )
{
foreach( Object obj in searchParameters.searchInAssetsSubset )
searchInAssetsSubset.Add( obj );
}
excludedAssets.Clear();
if( searchParameters.excludedAssetsFromSearch != null )
{
foreach( Object obj in searchParameters.excludedAssetsFromSearch )
excludedAssets.Add( obj );
}
excludedScenes.Clear();
if( searchParameters.excludedScenesFromSearch != null )
{
foreach( Object obj in searchParameters.excludedScenesFromSearch )
excludedScenes.Add( obj );
}
}
InitiateSearch();
Repaint();
}
private void Awake()
{
LoadPrefs();
}
private void OnEnable()
{
if( currentPhase == Phase.Complete && AssetUsageDetectorSettings.ShowCustomTooltip )
wantsMouseMove = wantsMouseEnterLeaveWindow = true; // These values aren't preserved during domain reload on Unity 2020.3.0f1
#if UNITY_2018_3_OR_NEWER
PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets;
#endif
}
private void OnDisable()
{
#if UNITY_2018_3_OR_NEWER
PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
#endif
SearchResultTooltip.Hide();
}
private void OnDestroy()
{
if( core != null )
core.SaveCache();
SavePrefs();
if( searchResult != null && currentPhase == Phase.Complete )
searchResult.RestoreInitialSceneSetup();
}
private void SavePrefs()
{
EditorPrefs.SetInt( PREFS_SEARCH_SCENES, (int) GetSceneSearchMode( false ) );
EditorPrefs.SetBool( PREFS_SEARCH_SCENE_LIGHTING_SETTINGS, searchInSceneLightingSettings );
EditorPrefs.SetBool( PREFS_SEARCH_ASSETS, searchInAssetsFolder );
EditorPrefs.SetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, dontSearchInSourceAssets );
EditorPrefs.SetBool( PREFS_SEARCH_PROJECT_SETTINGS, searchInProjectSettings );
EditorPrefs.SetInt( PREFS_SEARCH_DEPTH_LIMIT, searchDepthLimit );
EditorPrefs.SetInt( PREFS_SEARCH_FIELDS, (int) fieldModifiers );
EditorPrefs.SetInt( PREFS_SEARCH_PROPERTIES, (int) propertyModifiers );
EditorPrefs.SetBool( PREFS_SEARCH_NON_SERIALIZABLES, searchNonSerializableVariables );
EditorPrefs.SetBool( PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES, searchUnusedMaterialProperties );
EditorPrefs.SetBool( PREFS_LAZY_SCENE_SEARCH, lazySceneSearch );
#if ASSET_USAGE_ADDRESSABLES
EditorPrefs.SetBool( PREFS_ADDRESSABLES_SUPPORT, addressablesSupport );
#endif
EditorPrefs.SetBool( PREFS_CALCULATE_UNUSED_OBJECTS, calculateUnusedObjects );
EditorPrefs.SetBool( PREFS_HIDE_DUPLICATE_ROWS, hideDuplicateRows );
EditorPrefs.SetBool( PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS, hideReduntantPrefabVariantLinks );
EditorPrefs.SetBool( PREFS_SHOW_PROGRESS, showDetailedProgressBar );
}
private void LoadPrefs()
{
ParseSceneSearchMode( (SceneSearchMode) EditorPrefs.GetInt( PREFS_SEARCH_SCENES, (int) ( SceneSearchMode.OpenScenes | SceneSearchMode.ScenesInBuildSettingsTickedOnly | SceneSearchMode.AllScenes ) ) );
searchInSceneLightingSettings = EditorPrefs.GetBool( PREFS_SEARCH_SCENE_LIGHTING_SETTINGS, true );
searchInAssetsFolder = EditorPrefs.GetBool( PREFS_SEARCH_ASSETS, true );
dontSearchInSourceAssets = EditorPrefs.GetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, true );
searchInProjectSettings = EditorPrefs.GetBool( PREFS_SEARCH_PROJECT_SETTINGS, true );
searchDepthLimit = EditorPrefs.GetInt( PREFS_SEARCH_DEPTH_LIMIT, 4 );
fieldModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_FIELDS, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) );
propertyModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_PROPERTIES, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) );
searchNonSerializableVariables = EditorPrefs.GetBool( PREFS_SEARCH_NON_SERIALIZABLES, true );
searchUnusedMaterialProperties = EditorPrefs.GetBool( PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES, true );
lazySceneSearch = EditorPrefs.GetBool( PREFS_LAZY_SCENE_SEARCH, true );
#if ASSET_USAGE_ADDRESSABLES
addressablesSupport = EditorPrefs.GetBool( PREFS_ADDRESSABLES_SUPPORT, false );
#endif
calculateUnusedObjects = EditorPrefs.GetBool( PREFS_CALCULATE_UNUSED_OBJECTS, false );
hideDuplicateRows = EditorPrefs.GetBool( PREFS_HIDE_DUPLICATE_ROWS, true );
hideReduntantPrefabVariantLinks = EditorPrefs.GetBool( PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS, true );
showDetailedProgressBar = EditorPrefs.GetBool( PREFS_SHOW_PROGRESS, true );
}
private SceneSearchMode GetSceneSearchMode( bool hideOptionsInPlayMode )
{
SceneSearchMode sceneSearchMode = SceneSearchMode.None;
if( searchInOpenScenes )
sceneSearchMode |= SceneSearchMode.OpenScenes;
if( !hideOptionsInPlayMode || !EditorApplication.isPlaying )
{
if( searchInScenesInBuild )
sceneSearchMode |= searchInScenesInBuildTickedOnly ? SceneSearchMode.ScenesInBuildSettingsTickedOnly : SceneSearchMode.ScenesInBuildSettingsAll;
if( searchInAllScenes )
sceneSearchMode |= SceneSearchMode.AllScenes;
}
return sceneSearchMode;
}
private void ParseSceneSearchMode( SceneSearchMode sceneSearchMode )
{
searchInOpenScenes = ( sceneSearchMode & SceneSearchMode.OpenScenes ) == SceneSearchMode.OpenScenes;
searchInScenesInBuild = ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsAll ) == SceneSearchMode.ScenesInBuildSettingsAll || ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsTickedOnly ) == SceneSearchMode.ScenesInBuildSettingsTickedOnly;
searchInScenesInBuildTickedOnly = ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsAll ) != SceneSearchMode.ScenesInBuildSettingsAll;
searchInAllScenes = ( sceneSearchMode & SceneSearchMode.AllScenes ) == SceneSearchMode.AllScenes;
}
private void Update()
{
if( shouldRepositionSelf )
{
shouldRepositionSelf = false;
position = windowTargetPosition;
}
}
private void OnGUI()
{
// Make the window scrollable
scrollPosition = EditorGUILayout.BeginScrollView( scrollPosition, Utilities.GL_EXPAND_WIDTH, Utilities.GL_EXPAND_HEIGHT );
GUILayout.BeginVertical();
if( currentPhase == Phase.Processing )
{
// If we are stuck at this phase, then we have encountered an exception
GUILayout.Label( ". . . Search in progress or something went wrong (check console) . . ." );
if( GUILayout.Button( "RETURN", Utilities.GL_HEIGHT_30 ) )
{
ReturnToSetupPhase();
GUIUtility.ExitGUI();
}
}
else if( currentPhase == Phase.Setup )
{
DrawObjectsToSearchSection();
GUILayout.Space( 10f );
Color c = GUI.backgroundColor;
GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor;
GUILayout.Box( "<b>SEARCH IN</b>", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
GUI.backgroundColor = c;
searchInAssetsFolder = WordWrappingToggleLeft( "Project window (Assets folder)", searchInAssetsFolder );
if( searchInAssetsFolder )
{
GUILayout.BeginHorizontal();
GUILayout.Space( 35f );
GUILayout.BeginVertical();
searchInAssetsSubsetDrawer.Draw( searchInAssetsSubset );
excludedAssetsDrawer.Draw( excludedAssets );
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
GUILayout.Space( 5f );
dontSearchInSourceAssets = WordWrappingToggleLeft( "Don't search \"SEARCHED OBJECTS\" themselves for references", dontSearchInSourceAssets );
searchUnusedMaterialProperties = WordWrappingToggleLeft( "Search unused material properties (e.g. normal map of a material that no longer uses normal mapping)", searchUnusedMaterialProperties );
Utilities.DrawSeparatorLine();
if( searchInAllScenes && !EditorApplication.isPlaying )
GUI.enabled = false;
searchInOpenScenes = WordWrappingToggleLeft( "Currently open (loaded) scene(s)", searchInOpenScenes );
if( !EditorApplication.isPlaying )
{
searchInScenesInBuild = WordWrappingToggleLeft( "Scenes in Build Settings", searchInScenesInBuild );
if( searchInScenesInBuild )
{
GUILayout.BeginHorizontal();
GUILayout.Space( 35f );
searchInScenesInBuildTickedOnly = EditorGUILayout.ToggleLeft( "Ticked only", searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 );
searchInScenesInBuildTickedOnly = !EditorGUILayout.ToggleLeft( "All", !searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 );
GUILayout.EndHorizontal();
}
GUI.enabled = true;
searchInAllScenes = WordWrappingToggleLeft( "All scenes in the project", searchInAllScenes );
}
GUILayout.BeginHorizontal();
GUILayout.Space( 35f );
GUILayout.BeginVertical();
excludedScenesDrawer.Draw( excludedScenes );
GUILayout.EndVertical();
GUILayout.EndHorizontal();
EditorGUI.BeginDisabledGroup( !searchInOpenScenes && !searchInScenesInBuild && !searchInAllScenes );
searchInSceneLightingSettings = WordWrappingToggleLeft( "Scene Lighting Settings (WARNING: This may change the active scene during search)", searchInSceneLightingSettings );
EditorGUI.EndDisabledGroup();
Utilities.DrawSeparatorLine();
searchInProjectSettings = WordWrappingToggleLeft( "Project Settings (Player Settings, Graphics Settings etc.)", searchInProjectSettings );
GUILayout.Space( 10f );
GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor;
GUILayout.Box( "<b>SETTINGS</b>", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
GUI.backgroundColor = c;
#if ASSET_USAGE_ADDRESSABLES
EditorGUI.BeginDisabledGroup( addressablesSupport );
#endif
lazySceneSearch = WordWrappingToggleLeft( "Lazy scene search: scenes are searched in detail only when they are manually refreshed (faster search)", lazySceneSearch );
#if ASSET_USAGE_ADDRESSABLES
EditorGUI.EndDisabledGroup();
addressablesSupport = WordWrappingToggleLeft( "Addressables support (Experimental) (WARNING: 'Lazy scene search' will be disabled) (slower search)", addressablesSupport );
#endif
calculateUnusedObjects = WordWrappingToggleLeft( "Calculate unused objects", calculateUnusedObjects );
hideDuplicateRows = WordWrappingToggleLeft( "Hide duplicate rows in search results", hideDuplicateRows );
#if UNITY_2018_3_OR_NEWER
hideReduntantPrefabVariantLinks = WordWrappingToggleLeft( "Hide redundant prefab variant links (when the same value is assigned to the same Component of a prefab and its variant(s))", hideReduntantPrefabVariantLinks );
#endif
noAssetDatabaseChanges = WordWrappingToggleLeft( "I haven't modified any assets/scenes since the last search (faster search)", noAssetDatabaseChanges );
showDetailedProgressBar = WordWrappingToggleLeft( "Update search progress bar more often (cancelable search) (slower search)", showDetailedProgressBar );
GUILayout.Space( 10f );
// Don't let the user press the GO button without any valid search location
if( !searchInAllScenes && !searchInOpenScenes && !searchInScenesInBuild && !searchInAssetsFolder && !searchInProjectSettings )
GUI.enabled = false;
if( GUILayout.Button( "GO!", Utilities.GL_HEIGHT_30 ) )
{
InitiateSearch();
GUIUtility.ExitGUI();
}
GUILayout.Space( 5f );
}
else if( currentPhase == Phase.Complete )
{
// Draw the results of the search
GUI.enabled = false;
DrawObjectsToSearchSection();
if( drawObjectsToSearchSection )
GUILayout.Space( 10f );
GUI.enabled = true;
if( GUILayout.Button( "Reset Search", Utilities.GL_HEIGHT_30 ) )
{
ReturnToSetupPhase();
GUIUtility.ExitGUI();
}
if( searchResult == null )
{
EditorGUILayout.HelpBox( "ERROR: searchResult is null", MessageType.Error );
return;
}
else if( !searchResult.SearchCompletedSuccessfully )
EditorGUILayout.HelpBox( "ERROR: search was interrupted, check the logs for more info", MessageType.Error );
if( searchResult.NumberOfGroups == 0 )
{
GUILayout.Space( 10f );
GUILayout.Box( "No references found...", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
}
else
{
noAssetDatabaseChanges = WordWrappingToggleLeft( "I haven't modified any assets/scenes since the last search (faster Refresh)", noAssetDatabaseChanges );
EditorGUILayout.Space();
scrollPosition.y = searchResult.DrawOnGUI( this, scrollPosition.y, noAssetDatabaseChanges );
}
}
if( Event.current.type == EventType.MouseLeaveWindow )
{
SearchResultTooltip.Hide();
if( searchResult != null )
searchResult.CancelDelayedTreeViewTooltip();
}
GUILayout.EndVertical();
EditorGUILayout.EndScrollView();
}
private void DrawObjectsToSearchSection()
{
Color c = GUI.backgroundColor;
GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor;
GUILayout.Box( "<b>SEARCHED OBJECTS</b>", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
GUI.backgroundColor = c;
Rect searchedObjectsHeaderRect = GUILayoutUtility.GetLastRect();
searchedObjectsHeaderRect.x += 5f;
searchedObjectsHeaderRect.yMin += ( searchedObjectsHeaderRect.height - EditorGUIUtility.singleLineHeight ) * 0.5f;
searchedObjectsHeaderRect.height = EditorGUIUtility.singleLineHeight;
drawObjectsToSearchSection = EditorGUI.Foldout( searchedObjectsHeaderRect, drawObjectsToSearchSection, GUIContent.none, true );
if( drawObjectsToSearchSection )
objectsToSearchDrawer.Draw( objectsToSearch );
}
public static bool WordWrappingToggleLeft( string label, bool value )
{
GUILayout.BeginHorizontal();
bool result = EditorGUILayout.ToggleLeft( GUIContent.none, value, GL_WIDTH_12 );
if( GUILayout.Button( label, EditorStyles.wordWrappedLabel ) )
{
GUI.FocusControl( null );
result = !value;
}
GUILayout.EndHorizontal();
return result;
}
private void InitiateSearch()
{
currentPhase = Phase.Processing;
SavePrefs();
#if UNITY_2018_3_OR_NEWER
ReplacePrefabStageObjectsWithAssets( PrefabStageUtility.GetCurrentPrefabStage() );
#endif
// Start searching
searchResult = core.Run( new AssetUsageDetector.Parameters()
{
objectsToSearch = !objectsToSearch.IsEmpty() ? new ObjectToSearchEnumerator( objectsToSearch ).ToArray() : null,
searchInScenes = GetSceneSearchMode( true ),
searchInSceneLightingSettings = searchInSceneLightingSettings,
searchInAssetsFolder = searchInAssetsFolder,
searchInAssetsSubset = !searchInAssetsSubset.IsEmpty() ? searchInAssetsSubset.ToArray() : null,
excludedAssetsFromSearch = !excludedAssets.IsEmpty() ? excludedAssets.ToArray() : null,
dontSearchInSourceAssets = dontSearchInSourceAssets,
excludedScenesFromSearch = !excludedScenes.IsEmpty() ? excludedScenes.ToArray() : null,
searchInProjectSettings = searchInProjectSettings,
//fieldModifiers = fieldModifiers,
//propertyModifiers = propertyModifiers,
//searchDepthLimit = searchDepthLimit,
//searchNonSerializableVariables = searchNonSerializableVariables,
searchUnusedMaterialProperties = searchUnusedMaterialProperties,
searchRefactoring = searchRefactoring,
#if ASSET_USAGE_ADDRESSABLES
lazySceneSearch = lazySceneSearch && !addressablesSupport,
addressablesSupport = addressablesSupport,
#else
lazySceneSearch = lazySceneSearch,
#endif
calculateUnusedObjects = calculateUnusedObjects,
hideDuplicateRows = hideDuplicateRows,
hideReduntantPrefabVariantLinks = hideReduntantPrefabVariantLinks,
noAssetDatabaseChanges = noAssetDatabaseChanges,
showDetailedProgressBar = showDetailedProgressBar
} );
currentPhase = Phase.Complete;
// We really don't want SearchRefactoring to affect next searches unless the search is initiated via ShowAndSearch again
searchRefactoring = null;
if( AssetUsageDetectorSettings.ShowCustomTooltip )
wantsMouseMove = wantsMouseEnterLeaveWindow = true;
}
#if UNITY_2018_3_OR_NEWER
// Try replacing searched objects who are part of currently open prefab stage with their corresponding prefab assets
public void ReplacePrefabStageObjectsWithAssets( PrefabStage prefabStage )
{
if( prefabStage == null || !prefabStage.stageHandle.IsValid() )
return;
#if UNITY_2020_1_OR_NEWER
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.assetPath );
#else
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.prefabAssetPath );
#endif
if( prefabAsset == null || prefabAsset.Equals( null ) )
return;
for( int i = 0; i < objectsToSearch.Count; i++ )
{
Object obj = objectsToSearch[i].obj;
if( obj != null && !obj.Equals( null ) && obj is GameObject && prefabStage.IsPartOfPrefabContents( (GameObject) obj ) )
{
GameObject prefabStageObjectSource = ( (GameObject) obj ).FollowSymmetricHierarchy( prefabStage.prefabContentsRoot, prefabAsset );
if( prefabStageObjectSource != null )
objectsToSearch[i].obj = prefabStageObjectSource;
List<ObjectToSearch.SubAsset> subAssets = objectsToSearch[i].subAssets;
for( int j = 0; j < subAssets.Count; j++ )
{
obj = subAssets[j].subAsset;
if( obj != null && !obj.Equals( null ) && obj is GameObject && prefabStage.IsPartOfPrefabContents( (GameObject) obj ) )
{
prefabStageObjectSource = ( (GameObject) obj ).FollowSymmetricHierarchy( prefabStage.prefabContentsRoot, prefabAsset );
if( prefabStageObjectSource != null )
subAssets[j].subAsset = prefabStageObjectSource;
}
}
}
}
}
#endif
private bool ReturnToSetupPhase()
{
if( searchResult != null && !EditorApplication.isPlaying && !searchResult.RestoreInitialSceneSetup() )
return false;
searchResult = null;
currentPhase = Phase.Setup;
wantsMouseMove = wantsMouseEnterLeaveWindow = false;
SearchResultTooltip.Hide();
return true;
}
internal void OnSettingsChanged( bool highlightedSearchTextColorChanged = false, bool tooltipDescriptionsColorChanged = false )
{
if( searchResult == null )
return;
wantsMouseMove = wantsMouseEnterLeaveWindow = AssetUsageDetectorSettings.ShowCustomTooltip;
for( int i = searchResult.NumberOfGroups - 1; i >= 0; i-- )
{
if( searchResult[i].treeView != null )
{
searchResult[i].treeView.rowHeight = EditorGUIUtility.singleLineHeight + AssetUsageDetectorSettings.ExtraRowHeight;
searchResult[i].treeView.OnSettingsChanged( highlightedSearchTextColorChanged, tooltipDescriptionsColorChanged );
}
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 271a22c69c3d96c4dbdd04cca415a840
timeCreated: 1520032279
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public class EmptyEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
public T Current { get { return default( T ); } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
public void Reset() { }
public bool MoveNext()
{
return false;
}
public IEnumerator<T> GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
}
public class ObjectToSearchEnumerator : IEnumerable<Object>
{
public class Enumerator : IEnumerator<Object>
{
public Object Current
{
get
{
if( subAssetIndex < 0 )
return source[index].obj;
return source[index].subAssets[subAssetIndex].subAsset;
}
}
object IEnumerator.Current { get { return Current; } }
private List<ObjectToSearch> source;
private int index;
private int subAssetIndex;
public Enumerator( List<ObjectToSearch> source )
{
this.source = source;
Reset();
}
public void Dispose()
{
source = null;
}
public bool MoveNext()
{
if( subAssetIndex < -1 )
{
subAssetIndex = -1;
if( ++index >= source.Count )
return false;
// Skip folder assets in the enumeration, AssetUsageDetector expands encountered folders automatically
// and we don't want that to happen as source[index].subAssets already contains the folder's contents
if( !source[index].obj.IsFolder() )
return true;
}
List<ObjectToSearch.SubAsset> subAssets = source[index].subAssets;
if( subAssets != null )
{
while( ++subAssetIndex < subAssets.Count && !subAssets[subAssetIndex].shouldSearch )
continue;
if( subAssetIndex < subAssets.Count )
return true;
}
subAssetIndex = -2;
return MoveNext();
}
public void Reset()
{
index = -1;
subAssetIndex = -2;
}
}
private readonly List<ObjectToSearch> source;
public ObjectToSearchEnumerator( List<ObjectToSearch> source )
{
this.source = source;
}
public IEnumerator<Object> GetEnumerator()
{
return new Enumerator( source );
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Object[] ToArray()
{
int count = 0;
foreach( Object obj in this )
count++;
Object[] result = new Object[count];
int index = 0;
foreach( Object obj in this )
result[index++] = obj;
return result;
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 894047c47ce45cf40939dae24afcc72b
timeCreated: 1562079461
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,252 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public abstract class ListDrawer<T>
{
private readonly string label;
private readonly bool acceptSceneObjects;
protected ListDrawer( string label, bool acceptSceneObjects )
{
this.label = label;
this.acceptSceneObjects = acceptSceneObjects;
}
// Exposes a list on GUI
public bool Draw( List<T> list )
{
bool hasChanged = false;
bool guiEnabled = GUI.enabled;
Event ev = Event.current;
GUILayout.BeginHorizontal();
GUILayout.Label( label );
if( guiEnabled )
{
// Handle drag & drop references to array
// Credit: https://answers.unity.com/answers/657877/view.html
if( ( ev.type == EventType.DragPerform || ev.type == EventType.DragUpdated ) && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) )
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if( ev.type == EventType.DragPerform )
{
DragAndDrop.AcceptDrag();
Object[] draggedObjects = DragAndDrop.objectReferences;
if( draggedObjects.Length > 0 )
{
for( int i = 0; i < draggedObjects.Length; i++ )
{
if( draggedObjects[i] != null && !draggedObjects[i].Equals( null ) )
{
bool replacedNullElement = false;
for( int j = 0; j < list.Count; j++ )
{
if( IsElementNull( list[j] ) )
{
list[j] = CreateElement( draggedObjects[i] );
replacedNullElement = true;
break;
}
}
if( !replacedNullElement )
list.Add( CreateElement( draggedObjects[i] ) );
hasChanged = true;
}
}
}
}
ev.Use();
}
else if( ev.type == EventType.ContextClick && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) )
{
GenericMenu contextMenu = new GenericMenu();
contextMenu.AddItem( new GUIContent( "Clear" ), false, () =>
{
list.Clear();
list.Add( CreateElement( null ) );
} );
contextMenu.ShowAsContext();
ev.Use();
}
if( GUILayout.Button( "+", Utilities.GL_WIDTH_25 ) )
list.Insert( 0, CreateElement( null ) );
}
GUILayout.EndHorizontal();
for( int i = 0; i < list.Count; i++ )
{
T element = list[i];
GUI.changed = false;
GUILayout.BeginHorizontal();
Object prevObject = GetObjectFromElement( element );
Object newObject = EditorGUILayout.ObjectField( "", prevObject, typeof( Object ), acceptSceneObjects );
if( GUI.changed )
{
hasChanged = true;
SetObjectOfElement( list, i, newObject );
}
if( guiEnabled )
{
if( GUILayout.Button( "+", Utilities.GL_WIDTH_25 ) )
list.Insert( i + 1, CreateElement( null ) );
if( GUILayout.Button( "-", Utilities.GL_WIDTH_25 ) )
{
if( element != null && !element.Equals( null ) )
hasChanged = true;
// Lists with no elements look ugly, always keep a dummy null variable
if( list.Count > 1 )
list.RemoveAt( i-- );
else
list[0] = CreateElement( null );
}
}
GUILayout.EndHorizontal();
PostElementDrawer( element );
}
return hasChanged;
}
protected abstract T CreateElement( Object source );
protected abstract Object GetObjectFromElement( T element );
protected abstract void SetObjectOfElement( List<T> list, int index, Object value );
protected abstract bool IsElementNull( T element );
protected abstract void PostElementDrawer( T element );
}
public class ObjectListDrawer : ListDrawer<Object>
{
public ObjectListDrawer( string label, bool acceptSceneObjects ) : base( label, acceptSceneObjects )
{
}
protected override Object CreateElement( Object source )
{
return source;
}
protected override Object GetObjectFromElement( Object element )
{
return element;
}
protected override void SetObjectOfElement( List<Object> list, int index, Object value )
{
list[index] = value;
}
protected override bool IsElementNull( Object element )
{
return element == null || element.Equals( null );
}
protected override void PostElementDrawer( Object element )
{
}
}
public class ObjectToSearchListDrawer : ListDrawer<ObjectToSearch>
{
public ObjectToSearchListDrawer() : base( "Find references of:", true )
{
}
protected override ObjectToSearch CreateElement( Object source )
{
return new ObjectToSearch( source );
}
protected override Object GetObjectFromElement( ObjectToSearch element )
{
return element.obj;
}
protected override void SetObjectOfElement( List<ObjectToSearch> list, int index, Object value )
{
list[index].obj = value;
list[index].RefreshSubAssets();
}
protected override bool IsElementNull( ObjectToSearch element )
{
return element == null || element.obj == null || element.obj.Equals( null );
}
protected override void PostElementDrawer( ObjectToSearch element )
{
List<ObjectToSearch.SubAsset> subAssetsToSearch = element.subAssets;
if( subAssetsToSearch.Count > 0 )
{
GUILayout.BeginHorizontal();
// 0-> all toggles off, 1-> mixed, 2-> all toggles on
bool toggleAllSubAssets = subAssetsToSearch[0].shouldSearch;
bool mixedToggle = false;
for( int j = 1; j < subAssetsToSearch.Count; j++ )
{
if( subAssetsToSearch[j].shouldSearch != toggleAllSubAssets )
{
mixedToggle = true;
break;
}
}
if( mixedToggle )
EditorGUI.showMixedValue = true;
GUI.changed = false;
toggleAllSubAssets = EditorGUILayout.Toggle( toggleAllSubAssets, Utilities.GL_WIDTH_25 );
if( GUI.changed )
{
for( int j = 0; j < subAssetsToSearch.Count; j++ )
subAssetsToSearch[j].shouldSearch = toggleAllSubAssets;
}
EditorGUI.showMixedValue = false;
element.showSubAssetsFoldout = EditorGUILayout.Foldout( element.showSubAssetsFoldout, "Include sub-assets in search:", true );
GUILayout.EndHorizontal();
if( element.showSubAssetsFoldout )
{
for( int j = 0; j < subAssetsToSearch.Count; j++ )
{
GUILayout.BeginHorizontal();
subAssetsToSearch[j].shouldSearch = EditorGUILayout.Toggle( subAssetsToSearch[j].shouldSearch, Utilities.GL_WIDTH_25 );
bool guiEnabled = GUI.enabled;
GUI.enabled = false;
EditorGUILayout.ObjectField( string.Empty, subAssetsToSearch[j].subAsset, typeof( Object ), true );
GUI.enabled = guiEnabled;
GUILayout.EndHorizontal();
}
}
}
}
}
}

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 88a4a4e861026b2498a437ce1e12b054
timeCreated: 1568758673
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace AssetUsageDetectorNamespace
{
[Serializable]
public class ObjectToSearch
{
[Serializable]
public class SubAsset
{
public Object subAsset;
public bool shouldSearch;
public SubAsset( Object subAsset, bool shouldSearch )
{
this.subAsset = subAsset;
this.shouldSearch = shouldSearch;
}
}
public Object obj;
public List<SubAsset> subAssets;
public bool showSubAssetsFoldout;
private static HashSet<Object> currentSubAssets;
public ObjectToSearch( Object obj, bool? shouldSearchChildren = null )
{
this.obj = obj;
RefreshSubAssets( shouldSearchChildren );
}
public void RefreshSubAssets( bool? shouldSearchChildren = null )
{
if( subAssets == null )
subAssets = new List<SubAsset>();
else
subAssets.Clear();
if( currentSubAssets == null )
currentSubAssets = new HashSet<Object>();
else
currentSubAssets.Clear();
AddSubAssets( obj, false, shouldSearchChildren );
currentSubAssets.Clear();
}
private void AddSubAssets( Object target, bool includeTarget, bool? shouldSearchChildren )
{
if( target == null || target.Equals( null ) )
return;
if( !target.IsAsset() )
{
GameObject go = target as GameObject;
if( !go || !go.scene.IsValid() )
return;
// If this is a scene object, add its child objects to the sub-assets list
// but don't include them in the search by default
Transform goTransform = go.transform;
Transform[] children = go.GetComponentsInChildren<Transform>( true );
for( int i = 0; i < children.Length; i++ )
{
if( ReferenceEquals( children[i], goTransform ) )
continue;
subAssets.Add( new SubAsset( children[i].gameObject, shouldSearchChildren ?? false ) );
}
}
else
{
if( !AssetDatabase.IsMainAsset( target ) || target is SceneAsset )
return;
if( includeTarget )
{
if( currentSubAssets.Add( target ) )
subAssets.Add( new SubAsset( target, shouldSearchChildren ?? true ) );
}
else
{
// If asset is a directory, add all of its contents as sub-assets recursively
if( target.IsFolder() )
{
foreach( string filePath in Utilities.EnumerateFolderContents( target ) )
AddSubAssets( AssetDatabase.LoadAssetAtPath<Object>( filePath ), true, shouldSearchChildren );
return;
}
}
// Find sub-asset(s) of the asset (if any)
Object[] assets = AssetDatabase.LoadAllAssetsAtPath( AssetDatabase.GetAssetPath( target ) );
for( int i = 0; i < assets.Length; i++ )
{
Object asset = assets[i];
if( asset == null || asset.Equals( null ) || asset is Component || asset == target )
continue;
#if UNITY_2018_3_OR_NEWER
// Nested prefabs in prefab assets add an additional native object of type 'UnityEngine.PrefabInstance' to the prefab. Managed type of that native type
// is UnityEngine.Object (i.e. GetType() returns UnityEngine.Object, not UnityEngine.PrefabInstance). There are no possible references to these native
// objects so skip them (we're checking for UnityEngine.Prefab because it includes other native types like UnityEngine.PrefabCreation, as well)
if( target is GameObject && asset.GetType() == typeof( Object ) && asset.ToString().Contains( "(UnityEngine.Prefab" ) )
continue;
#endif
if( currentSubAssets.Add( asset ) )
subAssets.Add( new SubAsset( asset, shouldSearchChildren ?? true ) );
}
}
}
}
}

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save