com.wallstop-studios.unity-helpers 1.0.1-rc03 → 1.0.1-rc05

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/Editor/AnimationCopier.cs +124 -0
  2. package/Editor/AnimationCopier.cs.meta +3 -0
  3. package/Editor/AnimationCreator.cs +15 -12
  4. package/Editor/AnimationEventEditor.cs +1 -1
  5. package/Editor/CustomEditors/MatchColliderToSpriteEditor.cs +31 -0
  6. package/Editor/CustomEditors/MatchColliderToSpriteEditor.cs.meta +3 -0
  7. package/Editor/CustomEditors.meta +3 -0
  8. package/Editor/PrefabCheckWizard.cs +1 -1
  9. package/Editor/SpriteSettingsApplier.cs +154 -0
  10. package/Editor/SpriteSettingsApplier.cs.meta +3 -0
  11. package/Editor/TextureResizerWizard.cs +158 -0
  12. package/Editor/TextureResizerWizard.cs.meta +3 -0
  13. package/Editor/TextureSettingsApplier.cs +158 -0
  14. package/Editor/TextureSettingsApplier.cs.meta +3 -0
  15. package/Editor/Utils/EditorUtilities.cs +20 -0
  16. package/Editor/Utils/EditorUtilities.cs.meta +11 -0
  17. package/Editor/Utils/ReadOnlyPropertyDrawer.cs +28 -0
  18. package/Editor/Utils/ReadOnlyPropertyDrawer.cs.meta +11 -0
  19. package/Editor/Utils.meta +8 -0
  20. package/Runtime/Utils/CenterPointOffset.cs +31 -0
  21. package/Runtime/Utils/CenterPointOffset.cs.meta +3 -0
  22. package/Runtime/Utils/CoroutineHandler.cs +4 -0
  23. package/Runtime/Utils/CoroutineHandler.cs.meta +3 -0
  24. package/Runtime/Utils/MatchColliderToSprite.cs +80 -0
  25. package/Runtime/Utils/MatchColliderToSprite.cs.meta +3 -0
  26. package/Runtime/Utils/RuntimeSingleton.cs +31 -0
  27. package/Runtime/Utils/RuntimeSingleton.cs.meta +11 -0
  28. package/Runtime/Utils/SetTextureImportData.cs +4 -2
  29. package/Runtime/Utils/TextureScale.cs +164 -0
  30. package/Runtime/Utils/TextureScale.cs.meta +3 -0
  31. package/package.json +1 -1
@@ -0,0 +1,124 @@
1
+ namespace UnityHelpers.Editor
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.IO;
6
+ using Core.Attributes;
7
+ using Core.Extension;
8
+ using UnityEditor;
9
+ using UnityEngine;
10
+ using Utils;
11
+
12
+ public sealed class AnimationCopier : ScriptableWizard
13
+ {
14
+ private string _fullSourcePath;
15
+ private string _fullDestinationPath;
16
+
17
+ [ReadOnly]
18
+ public string animationSourcePath;
19
+
20
+ [ReadOnly]
21
+ public string animationDestinationPath;
22
+
23
+ [MenuItem("Tools/Unity Helpers/Animation Copier")]
24
+ public static void CopyAnimations()
25
+ {
26
+ _ = DisplayWizard<AnimationCopier>("Animation Copier", "Copy");
27
+ }
28
+
29
+ protected override bool DrawWizardGUI()
30
+ {
31
+ bool returnValue = base.DrawWizardGUI();
32
+
33
+ if (GUILayout.Button("Set Animation Source Path"))
34
+ {
35
+ string sourcePath = EditorUtility.OpenFolderPanel(
36
+ "Select Animation Source Path", EditorUtilities.GetCurrentPathOfProjectWindow(), string.Empty);
37
+ int assetIndex = sourcePath?.IndexOf("Assets", StringComparison.Ordinal) ?? -1;
38
+ if (assetIndex < 0)
39
+ {
40
+ return false;
41
+ }
42
+
43
+ _fullSourcePath = animationSourcePath = sourcePath ?? string.Empty;
44
+ animationSourcePath = animationSourcePath.Substring(assetIndex);
45
+ return true;
46
+ }
47
+
48
+ if (GUILayout.Button("Set Animation Destination Path"))
49
+ {
50
+ string sourcePath = EditorUtility.OpenFolderPanel(
51
+ "Select Animation Destination Path", EditorUtilities.GetCurrentPathOfProjectWindow(), string.Empty);
52
+ int assetIndex = sourcePath?.IndexOf("Assets", StringComparison.Ordinal) ?? -1;
53
+ if (assetIndex < 0)
54
+ {
55
+ return false;
56
+ }
57
+
58
+ _fullDestinationPath = animationDestinationPath = sourcePath ?? string.Empty;
59
+ animationDestinationPath = animationDestinationPath.Substring(assetIndex);
60
+ return true;
61
+ }
62
+
63
+ return returnValue;
64
+ }
65
+
66
+ private void OnWizardCreate()
67
+ {
68
+ if (string.IsNullOrEmpty(_fullSourcePath) || string.IsNullOrEmpty(_fullDestinationPath))
69
+ {
70
+ return;
71
+ }
72
+
73
+ if (string.IsNullOrEmpty(animationSourcePath) || string.IsNullOrEmpty(animationDestinationPath))
74
+ {
75
+ return;
76
+ }
77
+
78
+ foreach (string assetGuid in AssetDatabase.FindAssets("t:AnimationClip", new[] { animationSourcePath }))
79
+ {
80
+ string path = AssetDatabase.GUIDToAssetPath(assetGuid);
81
+
82
+ AnimationClip animationClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
83
+ if (animationClip == null)
84
+ {
85
+ this.LogError("Invalid AnimationClip (null) found at path '{0}', skipping.", path);
86
+ continue;
87
+ }
88
+
89
+ string prefix = animationClip.name;
90
+ string relativePath = path.Substring(animationSourcePath.Length);
91
+ int animIndex = relativePath.LastIndexOf(prefix, StringComparison.OrdinalIgnoreCase);
92
+ if (animIndex < 0)
93
+ {
94
+ this.LogWarn("Unsupported animation at '{0}', expected to be prefixed by '{1}'.", path, prefix);
95
+ continue;
96
+ }
97
+
98
+ string partialPath = relativePath.Substring(0, animIndex);
99
+ string outputPath = _fullDestinationPath + partialPath;
100
+
101
+ if (!Directory.Exists(outputPath))
102
+ {
103
+ _ = Directory.CreateDirectory(outputPath);
104
+ }
105
+
106
+ string destination = animationDestinationPath + partialPath + relativePath.Substring(animIndex);
107
+ bool copySuccessful = AssetDatabase.CopyAsset(path, destination);
108
+ if (copySuccessful)
109
+ {
110
+ bool deleteSuccessful = AssetDatabase.DeleteAsset(path);
111
+ if (!deleteSuccessful)
112
+ {
113
+ this.LogError("Failed to delete asset at path '{0}'.", path);
114
+ }
115
+ }
116
+ else
117
+ {
118
+ this.LogError("Failed to copy animation from '{0}' to '{1}'.", path, destination);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ #endif
124
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 40462f0dd8a4429e983409e3374ae66e
3
+ timeCreated: 1720665801
@@ -17,7 +17,6 @@
17
17
  public List<Texture2D> frames;
18
18
  public int framesPerSecond = DefaultFramesPerSecond;
19
19
  public string animationName;
20
- public string assetPath;
21
20
  }
22
21
 
23
22
  public sealed class AnimationCreator : ScriptableWizard
@@ -41,7 +40,7 @@
41
40
  AnimationData data = animationData[0];
42
41
 
43
42
  bool filled = false;
44
- if (data.frames is {Count: 0} && GUILayout.Button("Fill Sprites From Animation Sources"))
43
+ if (data.frames is { Count: 0 } && GUILayout.Button("Fill Sprites From Animation Sources"))
45
44
  {
46
45
  List<string> animationPaths = new();
47
46
  foreach (Object animationSource in animationSources)
@@ -49,7 +48,7 @@
49
48
  string assetPath = AssetDatabase.GetAssetPath(animationSource);
50
49
  animationPaths.Add(assetPath);
51
50
  }
52
-
51
+
53
52
  foreach (string assetGuid in AssetDatabase.FindAssets("t:sprite", animationPaths.ToArray()))
54
53
  {
55
54
  string path = AssetDatabase.GUIDToAssetPath(assetGuid);
@@ -63,14 +62,15 @@
63
62
  filled = true;
64
63
  }
65
64
 
66
- if (data.frames is { Count: > 0} && (filled || GUILayout.Button("Auto Parse Sprites")))
65
+ if (data.frames is { Count: > 0 } && (filled || GUILayout.Button("Auto Parse Sprites")))
67
66
  {
68
67
  Dictionary<string, Dictionary<string, List<Texture2D>>> texturesByPrefixAndAssetPath = new();
69
68
  foreach (Texture2D frame in data.frames)
70
69
  {
71
70
  string assetPathWithFrameName = AssetDatabase.GetAssetPath(frame);
72
71
  string frameName = frame.name;
73
- string assetPath = assetPathWithFrameName.Substring(0, assetPathWithFrameName.LastIndexOf(frameName, StringComparison.Ordinal));
72
+ string assetPath = assetPathWithFrameName.Substring(
73
+ 0, assetPathWithFrameName.LastIndexOf(frameName, StringComparison.Ordinal));
74
74
  int lastNumericIndex = frameName.Length - 1;
75
75
  for (int i = frameName.Length - 1; 0 <= i; --i)
76
76
  {
@@ -87,7 +87,8 @@
87
87
  : Math.Max(lastUnderscoreIndex, lastNumericIndex);
88
88
  if (0 < lastIndex)
89
89
  {
90
- Dictionary<string, List<Texture2D>> texturesByPrefix = texturesByPrefixAndAssetPath.GetOrAdd(assetPath);
90
+ Dictionary<string, List<Texture2D>> texturesByPrefix =
91
+ texturesByPrefixAndAssetPath.GetOrAdd(assetPath);
91
92
  string key = frameName.Substring(0, lastIndex);
92
93
  texturesByPrefix.GetOrAdd(key).Add(frame);
93
94
  }
@@ -100,16 +101,16 @@
100
101
  if (0 < texturesByPrefixAndAssetPath.Count)
101
102
  {
102
103
  animationData.Clear();
103
- foreach (KeyValuePair<string, Dictionary<string, List<Texture2D>>> assetPathAndTextures in texturesByPrefixAndAssetPath)
104
+ foreach (KeyValuePair<string, Dictionary<string, List<Texture2D>>> assetPathAndTextures in
105
+ texturesByPrefixAndAssetPath)
104
106
  {
105
- string assetPath = assetPathAndTextures.Key;
106
- foreach (KeyValuePair<string, List<Texture2D>> textureAndPrefix in assetPathAndTextures.Value)
107
+ foreach (KeyValuePair<string, List<Texture2D>> textureAndPrefix in assetPathAndTextures
108
+ .Value)
107
109
  {
108
110
  AnimationData newData = new()
109
111
  {
110
112
  frames = textureAndPrefix.Value,
111
113
  framesPerSecond = data.framesPerSecond,
112
- assetPath = assetPath,
113
114
  animationName = $"Anim_{textureAndPrefix.Key}"
114
115
  };
115
116
  animationData.Add(newData);
@@ -165,7 +166,8 @@
165
166
  int framesPerSecond = data.framesPerSecond;
166
167
  if (framesPerSecond <= 0)
167
168
  {
168
- this.LogWarn("Ignoring animationData with FPS of {0} with name {1}.", framesPerSecond, animationName);
169
+ this.LogWarn(
170
+ "Ignoring animationData with FPS of {0} with name {1}.", framesPerSecond, animationName);
169
171
  continue;
170
172
  }
171
173
 
@@ -205,7 +207,8 @@
205
207
  animationClip,
206
208
  EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"), keyFrames.ToArray());
207
209
  string assetPathWithFileNameAndExtension = AssetDatabase.GetAssetPath(frames[0]);
208
- string assetPath = assetPathWithFileNameAndExtension.Substring(0, assetPathWithFileNameAndExtension.LastIndexOf("/", StringComparison.Ordinal) + 1);
210
+ string assetPath = assetPathWithFileNameAndExtension.Substring(
211
+ 0, assetPathWithFileNameAndExtension.LastIndexOf("/", StringComparison.Ordinal) + 1);
209
212
 
210
213
  ProjectWindowUtil.CreateAsset(animationClip, assetPath + animationName + ".anim");
211
214
  }
@@ -9,7 +9,7 @@
9
9
  using Core.Helper;
10
10
  using UnityEngine;
11
11
  using UnityEditor;
12
- using Utils;
12
+ using UnityHelpers.Utils;
13
13
 
14
14
  // https://gist.githubusercontent.com/yujen/5e1cd78e2a341260b38029de08a449da/raw/ac60c1002e0e14375de5b2b0a167af00df3f74b4/SeniaAnimationEventEditor.cs
15
15
  public sealed class AnimationEventEditor : EditorWindow
@@ -0,0 +1,31 @@
1
+ namespace UnityHelpers.Editor.CustomEditors
2
+ {
3
+ #if UNITY_EDITOR
4
+ using Core.Extension;
5
+ using UnityEditor;
6
+ using UnityEngine;
7
+ using UnityHelpers.Utils;
8
+
9
+ [CustomEditor(typeof(MatchColliderToSprite))]
10
+ public sealed class MatchColliderToSpriteEditor : Editor
11
+ {
12
+ public override void OnInspectorGUI()
13
+ {
14
+ base.OnInspectorGUI();
15
+
16
+ MatchColliderToSprite matchColliderToSprite = target as MatchColliderToSprite;
17
+ if (matchColliderToSprite == null)
18
+ {
19
+ this.LogError(
20
+ "Target was of type {0}, expected {1}.", target?.GetType(), typeof(MatchColliderToSprite));
21
+ return;
22
+ }
23
+
24
+ if (GUILayout.Button("MatchColliderToSprite"))
25
+ {
26
+ matchColliderToSprite.OnValidate();
27
+ }
28
+ }
29
+ }
30
+ #endif
31
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 47c2b6576aee42869685c6b9c1d565bd
3
+ timeCreated: 1720738619
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 2edcc08721b94e54b6d2795c2000eee6
3
+ timeCreated: 1720738611
@@ -11,7 +11,7 @@
11
11
  using UnityEngine;
12
12
  using Core.Extension;
13
13
  using Core.Helper;
14
- using Utils;
14
+ using UnityHelpers.Utils;
15
15
  using Object = UnityEngine.Object;
16
16
 
17
17
  public sealed class PrefabCheckWizard : ScriptableWizard
@@ -0,0 +1,154 @@
1
+ namespace UnityHelpers.Editor
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Collections.Generic;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using Core.Extension;
9
+ using UnityEditor;
10
+ using UnityEngine;
11
+ using Object = UnityEngine.Object;
12
+
13
+ [Serializable]
14
+ public sealed class SpriteSettings
15
+ {
16
+ public int pixelsPerUnit = 100;
17
+ public Vector2 pivot = new(0.5f, 0.5f);
18
+ public SpriteImportMode spriteMode = SpriteImportMode.Single;
19
+ public bool applyWrapMode = true;
20
+ public TextureWrapMode wrapMode = TextureWrapMode.Clamp;
21
+ public bool applyFilterMode = true;
22
+ public FilterMode filterMode = FilterMode.Point;
23
+ public string name;
24
+ }
25
+
26
+ public sealed class SpriteSettingsApplier : ScriptableWizard
27
+ {
28
+ public List<Sprite> sprites = new();
29
+ public List<string> spriteFileExtensions = new() { ".png" };
30
+
31
+ [Tooltip(
32
+ "Drag various sprite settings here, where the name property matches a sprite asset name. The first settings with an empty or matching name will be applied to each and every sprite.")]
33
+ public List<SpriteSettings> spriteSettings = new() { new SpriteSettings() };
34
+
35
+ [Tooltip(
36
+ "Drag a folder from Unity here to apply the configuration to all settings under it. No sprites are modified if no directories are provided.")]
37
+ public List<Object> directories = new();
38
+
39
+ [MenuItem("Tools/Unity Helpers/Sprite Settings Applier")]
40
+ public static void CreateAnimation()
41
+ {
42
+ _ = DisplayWizard<SpriteSettingsApplier>("Sprite Settings Directory Applier", "Set");
43
+ }
44
+
45
+ private void OnWizardCreate()
46
+ {
47
+ HashSet<string> uniqueDirectories = new();
48
+ foreach (Object directory in directories)
49
+ {
50
+ string assetPath = AssetDatabase.GetAssetPath(directory);
51
+ if (Directory.Exists(assetPath))
52
+ {
53
+ _ = uniqueDirectories.Add(assetPath);
54
+ }
55
+ }
56
+
57
+ HashSet<string> processedSpritePaths = new();
58
+ Queue<string> directoriesToCheck = new(uniqueDirectories);
59
+ int spriteCount = 0;
60
+ while (0 < directoriesToCheck.Count)
61
+ {
62
+ string directoryPath = directoriesToCheck.Dequeue();
63
+ foreach (string fullFilePath in Directory.EnumerateFiles(directoryPath))
64
+ {
65
+ if (!spriteFileExtensions.Contains(Path.GetExtension(fullFilePath)))
66
+ {
67
+ continue;
68
+ }
69
+
70
+ int index = fullFilePath.LastIndexOf(directoryPath, StringComparison.OrdinalIgnoreCase);
71
+ if (index < 0)
72
+ {
73
+ continue;
74
+ }
75
+
76
+ string filePath = fullFilePath.Substring(index);
77
+ if (processedSpritePaths.Add(fullFilePath) && TryUpdateTextureSettings(filePath))
78
+ {
79
+ ++spriteCount;
80
+ }
81
+ }
82
+
83
+ foreach (string subDirectory in Directory.EnumerateDirectories(directoryPath))
84
+ {
85
+ int index = subDirectory.LastIndexOf(directoryPath, StringComparison.OrdinalIgnoreCase);
86
+ if (index < 0)
87
+ {
88
+ continue;
89
+ }
90
+
91
+ directoriesToCheck.Enqueue(subDirectory.Substring(index));
92
+ }
93
+ }
94
+
95
+ foreach (Sprite sprite in sprites)
96
+ {
97
+ if (sprite == null)
98
+ {
99
+ continue;
100
+ }
101
+
102
+ string filePath = AssetDatabase.GetAssetPath(sprite);
103
+ if (processedSpritePaths.Add(Application.dataPath + filePath) && TryUpdateTextureSettings(filePath))
104
+ {
105
+ ++spriteCount;
106
+ }
107
+ }
108
+
109
+ this.Log("Processed {0} sprites.", spriteCount);
110
+ if (0 < spriteCount)
111
+ {
112
+ AssetDatabase.Refresh();
113
+ }
114
+ }
115
+
116
+ private bool TryUpdateTextureSettings(string filePath)
117
+ {
118
+ TextureImporter textureImporter = AssetImporter.GetAtPath(filePath) as TextureImporter;
119
+ if (textureImporter == null)
120
+ {
121
+ return false;
122
+ }
123
+
124
+ SpriteSettings spriteData = spriteSettings.FirstOrDefault(
125
+ settings => string.IsNullOrEmpty(settings.name) || filePath.Contains(settings.name));
126
+ if (spriteData == null)
127
+ {
128
+ return false;
129
+ }
130
+
131
+ textureImporter.spritePivot = spriteData.pivot;
132
+ textureImporter.spritePixelsPerUnit = spriteData.pixelsPerUnit;
133
+
134
+ TextureImporterSettings settings = new();
135
+ textureImporter.ReadTextureSettings(settings);
136
+ settings.spriteAlignment = (int)SpriteAlignment.Custom;
137
+ settings.spriteMode = (int)spriteData.spriteMode;
138
+ if (spriteData.applyWrapMode)
139
+ {
140
+ settings.wrapMode = spriteData.wrapMode;
141
+ }
142
+
143
+ if (spriteData.applyFilterMode)
144
+ {
145
+ settings.filterMode = spriteData.filterMode;
146
+ }
147
+
148
+ textureImporter.SetTextureSettings(settings);
149
+ textureImporter.SaveAndReimport();
150
+ return true;
151
+ }
152
+ }
153
+ #endif
154
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: d9b66c9d9bef484c9c916fc474d24c86
3
+ timeCreated: 1720665873
@@ -0,0 +1,158 @@
1
+ namespace UnityHelpers.Editor
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Collections.Generic;
6
+ using System.ComponentModel;
7
+ using System.IO;
8
+ using System.Linq;
9
+ using Core.Extension;
10
+ using Core.Helper;
11
+ using UnityEditor;
12
+ using UnityEngine;
13
+ using UnityEngine.Serialization;
14
+ using UnityHelpers.Utils;
15
+ using Utils;
16
+ using Object = UnityEngine.Object;
17
+
18
+ public sealed class TextureResizerWizard : ScriptableWizard
19
+ {
20
+ public enum ResizeAlgorithm
21
+ {
22
+ Bilinear,
23
+ Point
24
+ }
25
+
26
+ public List<Texture2D> textures = new();
27
+
28
+ [FormerlySerializedAs("animationSources")]
29
+ [Tooltip(
30
+ "Drag a folder from Unity here to apply the configuration to all textures under it. No textures are modified if no directories are provided.")]
31
+ public List<Object> textureSourcePaths = new();
32
+
33
+ public int numResizes = 1;
34
+
35
+ [Tooltip("Resize algorithm to use for scaling.")]
36
+ public ResizeAlgorithm scalingResizeAlgorithm = ResizeAlgorithm.Bilinear;
37
+
38
+ public int pixelsPerUnit = 100;
39
+ public float widthMultiplier = 0.54f;
40
+ public float heightMultiplier = 0.245f;
41
+
42
+ [MenuItem("Tools/Unity Helpers/Texture Resizer")]
43
+ public static void ResizeTextures()
44
+ {
45
+ _ = DisplayWizard<TextureResizerWizard>("Texture Resizer", "Resize");
46
+ }
47
+
48
+ private void OnWizardCreate()
49
+ {
50
+ textures ??= new List<Texture2D>();
51
+ textureSourcePaths ??= new List<Object>();
52
+ HashSet<string> animationPaths = new();
53
+ foreach (Object animationSource in textureSourcePaths)
54
+ {
55
+ string assetPath = AssetDatabase.GetAssetPath(animationSource);
56
+ if (!string.IsNullOrEmpty(assetPath))
57
+ {
58
+ _ = animationPaths.Add(assetPath);
59
+ }
60
+ }
61
+
62
+ if (animationPaths.Any())
63
+ {
64
+ foreach (string assetGuid in AssetDatabase.FindAssets("t:texture2D", animationPaths.ToArray()))
65
+ {
66
+ string path = AssetDatabase.GUIDToAssetPath(assetGuid);
67
+ if (string.IsNullOrEmpty(path))
68
+ {
69
+ continue;
70
+ }
71
+
72
+ Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
73
+ if (texture != null)
74
+ {
75
+ textures.Add(texture);
76
+ }
77
+ }
78
+ }
79
+
80
+ textures = textures.Distinct().OrderBy(texture => texture.name).ToList();
81
+ if (textures.Count <= 0 || numResizes <= 0)
82
+ {
83
+ return;
84
+ }
85
+
86
+ for (int i = 0; i < numResizes; ++i)
87
+ {
88
+ foreach (Texture2D inputTexture in textures)
89
+ {
90
+ Texture2D texture = inputTexture;
91
+ string assetPath = AssetDatabase.GetAssetPath(texture);
92
+ if (string.IsNullOrEmpty(assetPath))
93
+ {
94
+ continue;
95
+ }
96
+
97
+ TextureImporter tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
98
+ if (tImporter == null)
99
+ {
100
+ continue;
101
+ }
102
+
103
+ bool isReadable = tImporter.isReadable;
104
+ if (!isReadable)
105
+ {
106
+ tImporter.isReadable = true;
107
+ tImporter.SaveAndReimport();
108
+ AssetDatabase.Refresh();
109
+ texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
110
+ }
111
+
112
+ Texture2D copy = Instantiate(texture);
113
+ try
114
+ {
115
+ int extraWidth = (int)Math.Round(copy.width / (pixelsPerUnit * widthMultiplier));
116
+ int extraHeight = (int)Math.Round(
117
+ copy.height / (pixelsPerUnit * heightMultiplier));
118
+ if (extraWidth == 0 && extraHeight == 0)
119
+ {
120
+ continue;
121
+ }
122
+
123
+ switch (scalingResizeAlgorithm)
124
+ {
125
+ case ResizeAlgorithm.Bilinear:
126
+ TextureScale.Bilinear(copy, copy.width + extraWidth, copy.height + extraHeight);
127
+ break;
128
+ case ResizeAlgorithm.Point:
129
+ TextureScale.Point(copy, copy.width + extraWidth, copy.height + extraHeight);
130
+ break;
131
+ default:
132
+ throw new InvalidEnumArgumentException(
133
+ nameof(scalingResizeAlgorithm), (int)scalingResizeAlgorithm,
134
+ typeof(ResizeAlgorithm));
135
+ }
136
+
137
+ byte[] bytes = copy.EncodeToPNG();
138
+ File.WriteAllBytes(assetPath, bytes);
139
+ this.Log(
140
+ "Resized {0} from [{1}x{2}] to [{3}x{4}]", texture.name, texture.width, texture.height,
141
+ copy.width, copy.height);
142
+ }
143
+ catch (Exception e)
144
+ {
145
+ this.LogError("Failed to resize {0}.", e, texture.name);
146
+ }
147
+ finally
148
+ {
149
+ copy.Destroy();
150
+ }
151
+ }
152
+
153
+ AssetDatabase.Refresh();
154
+ }
155
+ }
156
+ }
157
+ #endif
158
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 7c92d51f5bf348dfac9d569358f42c13
3
+ timeCreated: 1720666783
@@ -0,0 +1,158 @@
1
+ namespace UnityHelpers.Editor
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Collections.Generic;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using Core.Extension;
9
+ using UnityEditor;
10
+ using UnityEngine;
11
+ using UnityHelpers.Utils;
12
+ using Object = UnityEngine.Object;
13
+
14
+ public sealed class TextureSettingsApplier : ScriptableWizard
15
+ {
16
+ public bool applyReadOnly = false;
17
+ public bool isReadOnly = false;
18
+ public bool applyMipMaps = false;
19
+ public bool generateMipMaps = false;
20
+ public bool applyWrapMode = false;
21
+ public TextureWrapMode wrapMode = TextureWrapMode.Clamp;
22
+ public bool applyFilterMode = false;
23
+ public FilterMode filterMode = FilterMode.Trilinear;
24
+ public TextureImporterCompression compression = TextureImporterCompression.CompressedHQ;
25
+ public bool useCrunchCompression = true;
26
+ public TextureResizeAlgorithm textureResizeAlgorithm = TextureResizeAlgorithm.Bilinear;
27
+ public int maxTextureSize = SetTextureImportData.MaxTextureSize;
28
+ public TextureImporterFormat textureFormat = TextureImporterFormat.Automatic;
29
+ public List<string> spriteFileExtensions = new() { ".png" };
30
+
31
+ public List<Texture2D> textures = new();
32
+
33
+ [Tooltip(
34
+ "Drag a folder from Unity here to apply the configuration to all settings under it. No sprites are modified if no directories are provided.")]
35
+ public List<Object> directories = new();
36
+
37
+ [MenuItem("Tools/Unity Helpers/Texture Settings Applier")]
38
+ public static void CreateAnimation()
39
+ {
40
+ _ = DisplayWizard<TextureSettingsApplier>("Texture Settings Directory Applier", "Set");
41
+ }
42
+
43
+ private void OnWizardCreate()
44
+ {
45
+ HashSet<string> uniqueDirectories = new();
46
+ foreach (Object directory in directories)
47
+ {
48
+ string assetPath = AssetDatabase.GetAssetPath(directory);
49
+ if (Directory.Exists(assetPath))
50
+ {
51
+ _ = uniqueDirectories.Add(assetPath);
52
+ }
53
+ }
54
+
55
+ int textureCount = 0;
56
+ HashSet<string> processedPaths = new();
57
+ foreach (Texture2D texture in textures?
58
+ .Distinct()
59
+ .OrderBy(texture => texture != null ? texture.name : string.Empty) ??
60
+ Enumerable.Empty<Texture2D>())
61
+ {
62
+ if (texture == null)
63
+ {
64
+ continue;
65
+ }
66
+
67
+ string assetPath = AssetDatabase.GetAssetPath(texture);
68
+ if (processedPaths.Add(Application.dataPath + assetPath) && TryUpdateTextureSettings(assetPath))
69
+ {
70
+ ++textureCount;
71
+ }
72
+ }
73
+
74
+ Queue<string> directoriesToCheck = new(uniqueDirectories);
75
+ while (directoriesToCheck.TryDequeue(out string directoryPath))
76
+ {
77
+ foreach (string fullFilePath in Directory.EnumerateFiles(directoryPath))
78
+ {
79
+ if (!spriteFileExtensions.Contains(Path.GetExtension(fullFilePath)))
80
+ {
81
+ continue;
82
+ }
83
+
84
+ int index = fullFilePath.LastIndexOf(directoryPath, StringComparison.OrdinalIgnoreCase);
85
+ if (index < 0)
86
+ {
87
+ continue;
88
+ }
89
+
90
+ string filePath = fullFilePath.Substring(index);
91
+ if (processedPaths.Add(fullFilePath) && TryUpdateTextureSettings(filePath))
92
+ {
93
+ ++textureCount;
94
+ }
95
+ }
96
+
97
+ foreach (string subDirectory in Directory.EnumerateDirectories(directoryPath))
98
+ {
99
+ int index = subDirectory.LastIndexOf(directoryPath, StringComparison.OrdinalIgnoreCase);
100
+ if (index < 0)
101
+ {
102
+ continue;
103
+ }
104
+
105
+ directoriesToCheck.Enqueue(subDirectory.Substring(index));
106
+ }
107
+ }
108
+
109
+ this.Log("Processed {0} textures.", textureCount);
110
+ if (0 < textureCount)
111
+ {
112
+ AssetDatabase.Refresh();
113
+ }
114
+ }
115
+
116
+ private bool TryUpdateTextureSettings(string filePath)
117
+ {
118
+ TextureImporter textureImporter = AssetImporter.GetAtPath(filePath) as TextureImporter;
119
+ if (textureImporter == null)
120
+ {
121
+ return false;
122
+ }
123
+
124
+ textureImporter.SetPlatformTextureSettings(
125
+ new TextureImporterPlatformSettings
126
+ {
127
+ resizeAlgorithm = textureResizeAlgorithm,
128
+ maxTextureSize = maxTextureSize,
129
+ format = textureFormat,
130
+ textureCompression = compression,
131
+ crunchedCompression = useCrunchCompression,
132
+ });
133
+ if (applyReadOnly)
134
+ {
135
+ textureImporter.isReadable = !isReadOnly;
136
+ }
137
+
138
+ if (applyMipMaps)
139
+ {
140
+ textureImporter.mipmapEnabled = generateMipMaps;
141
+ }
142
+
143
+ if (applyWrapMode)
144
+ {
145
+ textureImporter.wrapMode = wrapMode;
146
+ }
147
+
148
+ if (applyFilterMode)
149
+ {
150
+ textureImporter.filterMode = filterMode;
151
+ }
152
+
153
+ textureImporter.SaveAndReimport();
154
+ return true;
155
+ }
156
+ }
157
+ #endif
158
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 23e3474825574ecfa1084d4d4ca83ebd
3
+ timeCreated: 1720666563
@@ -0,0 +1,20 @@
1
+ namespace UnityHelpers.Editor.Utils
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Reflection;
6
+ using UnityEditor;
7
+
8
+ public static class EditorUtilities
9
+ {
10
+ public static string GetCurrentPathOfProjectWindow()
11
+ {
12
+ Type projectWindowUtilType = typeof(ProjectWindowUtil);
13
+ MethodInfo getActiveFolderPath = projectWindowUtilType.GetMethod(
14
+ "GetActiveFolderPath", BindingFlags.Static | BindingFlags.NonPublic);
15
+ object obj = getActiveFolderPath?.Invoke(null, Array.Empty<object>());
16
+ return obj?.ToString() ?? string.Empty;
17
+ }
18
+ }
19
+ #endif
20
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 9256940fb08e3b847a327d916cdce82c
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,28 @@
1
+ namespace UnityHelpers.Editor.Utils
2
+ {
3
+ #if UNITY_EDITOR
4
+ using Core.Attributes;
5
+ using UnityEditor;
6
+ using UnityEngine;
7
+
8
+ // https://www.patrykgalach.com/2020/01/20/readonly-attribute-in-unity-editor/
9
+ [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
10
+ public sealed class ReadOnlyPropertyDrawer : PropertyDrawer
11
+ {
12
+ public override float GetPropertyHeight(
13
+ SerializedProperty property,
14
+ GUIContent label)
15
+ {
16
+ return EditorGUI.GetPropertyHeight(property, label, true);
17
+ }
18
+
19
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
20
+ {
21
+ bool previousGUIState = GUI.enabled;
22
+ GUI.enabled = false;
23
+ _ = EditorGUI.PropertyField(position, property, label);
24
+ GUI.enabled = previousGUIState;
25
+ }
26
+ }
27
+ #endif
28
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: c10f5b90c98ae0d48a81cf8d36a9d9ef
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: b8d1e55b56715f64db1fa9dabc1cb498
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,31 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ using Core.Attributes;
4
+ using UnityEngine;
5
+
6
+ [DisallowMultipleComponent]
7
+ public sealed class CenterPointOffset : MonoBehaviour
8
+ {
9
+ public Vector2 offset = Vector2.zero;
10
+
11
+ public bool spriteUsesOffset = true;
12
+
13
+ [SiblingComponent]
14
+ private Transform _transform;
15
+
16
+ private void Awake()
17
+ {
18
+ this.AssignSiblingComponents();
19
+ }
20
+
21
+ public Vector2 CenterPoint
22
+ {
23
+ get
24
+ {
25
+ Vector2 scaledOffset = offset * _transform.localScale;
26
+ return (Vector2)_transform.position + scaledOffset;
27
+ }
28
+ }
29
+ }
30
+
31
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: f1e1dfbe10214f8b8f30acd34d67aa7f
3
+ timeCreated: 1720737954
@@ -0,0 +1,4 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ public sealed class CoroutineHandler : RuntimeSingleton<CoroutineHandler> { }
4
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: ba52cce99edd4592999c269422e4f20a
3
+ timeCreated: 1720740992
@@ -0,0 +1,80 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using Core.Attributes;
6
+ using Core.Extension;
7
+ using UnityEngine;
8
+
9
+ [DisallowMultipleComponent]
10
+ public sealed class MatchColliderToSprite : MonoBehaviour
11
+ {
12
+ [SerializeField]
13
+ [SiblingComponent]
14
+ private SpriteRenderer _spriteRenderer;
15
+
16
+ [SerializeField]
17
+ [SiblingComponent]
18
+ private PolygonCollider2D _collider;
19
+
20
+ private Sprite _lastHandled;
21
+
22
+ private void Awake()
23
+ {
24
+ this.AssignSiblingComponents();
25
+ OnValidate();
26
+ }
27
+
28
+ private void Update()
29
+ {
30
+ if (_lastHandled == _spriteRenderer.sprite)
31
+ {
32
+ return;
33
+ }
34
+
35
+ OnValidate();
36
+ }
37
+
38
+ // Visible for testing
39
+ public void OnValidate()
40
+ {
41
+ if (_spriteRenderer == null)
42
+ {
43
+ _spriteRenderer = GetComponent<SpriteRenderer>();
44
+ if (_spriteRenderer == null)
45
+ {
46
+ this.LogError("No SpriteRenderer detected - cannot match collider shape.");
47
+ return;
48
+ }
49
+ }
50
+
51
+ if (_collider == null)
52
+ {
53
+ _collider = GetComponent<PolygonCollider2D>();
54
+ if (_collider == null)
55
+ {
56
+ this.LogError("No PolygonCollider2D detected - cannot match collider shape.");
57
+ return;
58
+ }
59
+ }
60
+
61
+ _lastHandled = _spriteRenderer.sprite;
62
+ _collider.points = Array.Empty<Vector2>();
63
+ if (_lastHandled == null)
64
+ {
65
+ _collider.pathCount = 0;
66
+ return;
67
+ }
68
+
69
+ int physicsShapes = _lastHandled.GetPhysicsShapeCount();
70
+ _collider.pathCount = physicsShapes;
71
+ List<Vector2> buffer = Buffers<Vector2>.List;
72
+ for (int i = 0; i < physicsShapes; ++i)
73
+ {
74
+ buffer.Clear();
75
+ _ = _lastHandled.GetPhysicsShape(i, buffer);
76
+ _collider.SetPath(i, buffer);
77
+ }
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 90f375924f7247ab87a73467faae06b7
3
+ timeCreated: 1720738383
@@ -0,0 +1,31 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ using UnityEngine;
4
+
5
+ [DisallowMultipleComponent]
6
+ public abstract class RuntimeSingleton<T> : MonoBehaviour where T : RuntimeSingleton<T>
7
+ {
8
+ private static T _instance;
9
+
10
+ protected virtual bool Preserve => true;
11
+
12
+ public static T Instance
13
+ {
14
+ get
15
+ {
16
+ if (_instance != null)
17
+ {
18
+ return _instance;
19
+ }
20
+
21
+ GameObject instance = new(typeof(T).Name + "Singleton", typeof(T));
22
+ if (instance.TryGetComponent(out _instance) && _instance.Preserve)
23
+ {
24
+ DontDestroyOnLoad(instance);
25
+ }
26
+
27
+ return _instance;
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: cbebf3402056afd46a0e23363875c813
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -17,6 +17,7 @@
17
17
  {
18
18
  return;
19
19
  }
20
+
20
21
  TextureImporter tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
21
22
 
22
23
  if (tImporter == null)
@@ -42,13 +43,14 @@
42
43
  {
43
44
  return;
44
45
  }
46
+
45
47
  TextureImporter tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
46
48
 
47
49
  if (tImporter == null)
48
50
  {
49
51
  return;
50
52
  }
51
-
53
+
52
54
  tImporter.isReadable = isReadable;
53
55
 
54
56
  TextureImporterPlatformSettings importerSettings = new TextureImporterPlatformSettings
@@ -64,4 +66,4 @@
64
66
  # endif
65
67
  }
66
68
  }
67
- }
69
+ }
@@ -0,0 +1,164 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ using System.Threading;
4
+ using UnityEngine;
5
+
6
+ // https://answers.unity.com/questions/348163/resize-texture2d-comes-out-grey.html
7
+ // http://wiki.unity3d.com/index.php/TextureScale
8
+ public static class TextureScale
9
+ {
10
+ private sealed class ThreadData
11
+ {
12
+ public readonly int start;
13
+ public readonly int end;
14
+
15
+ public ThreadData(int s, int e)
16
+ {
17
+ start = s;
18
+ end = e;
19
+ }
20
+ }
21
+
22
+ private static Color[] texColors;
23
+ private static Color[] newColors;
24
+ private static int w;
25
+ private static float ratioX;
26
+ private static float ratioY;
27
+ private static int w2;
28
+ private static int finishCount;
29
+ private static Mutex mutex;
30
+
31
+ public static void Point(Texture2D tex, int newWidth, int newHeight)
32
+ {
33
+ ThreadedScale(tex, newWidth, newHeight, false);
34
+ }
35
+
36
+ public static void Bilinear(Texture2D tex, int newWidth, int newHeight)
37
+ {
38
+ ThreadedScale(tex, newWidth, newHeight, true);
39
+ }
40
+
41
+ private static void ThreadedScale(Texture2D tex, int newWidth, int newHeight, bool useBilinear)
42
+ {
43
+ texColors = tex.GetPixels();
44
+ newColors = new Color[newWidth * newHeight];
45
+ if (useBilinear)
46
+ {
47
+ ratioX = 1.0f / ((float)newWidth / (tex.width - 1));
48
+ ratioY = 1.0f / ((float)newHeight / (tex.height - 1));
49
+ }
50
+ else
51
+ {
52
+ ratioX = ((float)tex.width) / newWidth;
53
+ ratioY = ((float)tex.height) / newHeight;
54
+ }
55
+
56
+ w = tex.width;
57
+ w2 = newWidth;
58
+ int cores = Mathf.Min(SystemInfo.processorCount, newHeight);
59
+ int slice = newHeight / cores;
60
+
61
+ finishCount = 0;
62
+ mutex ??= new Mutex(false);
63
+ if (1 < cores)
64
+ {
65
+ int i;
66
+ ThreadData threadData;
67
+ for (i = 0; i < cores - 1; i++)
68
+ {
69
+ threadData = new ThreadData(slice * i, slice * (i + 1));
70
+ ParameterizedThreadStart ts = useBilinear ? BilinearScale : PointScale;
71
+ Thread thread = new(ts);
72
+ thread.Start(threadData);
73
+ }
74
+
75
+ threadData = new ThreadData(slice * i, newHeight);
76
+ if (useBilinear)
77
+ {
78
+ BilinearScale(threadData);
79
+ }
80
+ else
81
+ {
82
+ PointScale(threadData);
83
+ }
84
+
85
+ while (finishCount < cores)
86
+ {
87
+ Thread.Sleep(1);
88
+ }
89
+ }
90
+ else
91
+ {
92
+ ThreadData threadData = new(0, newHeight);
93
+ if (useBilinear)
94
+ {
95
+ BilinearScale(threadData);
96
+ }
97
+ else
98
+ {
99
+ PointScale(threadData);
100
+ }
101
+ }
102
+
103
+ _ = tex.Reinitialize(newWidth, newHeight);
104
+ tex.SetPixels(newColors);
105
+ tex.Apply();
106
+
107
+ texColors = null;
108
+ newColors = null;
109
+ }
110
+
111
+ private static void BilinearScale(System.Object obj)
112
+ {
113
+ ThreadData threadData = (ThreadData)obj;
114
+ for (int y = threadData.start; y < threadData.end; y++)
115
+ {
116
+ int yFloor = (int)Mathf.Floor(y * ratioY);
117
+ int y1 = yFloor * w;
118
+ int y2 = (yFloor + 1) * w;
119
+ int yw = y * w2;
120
+
121
+ for (var x = 0; x < w2; x++)
122
+ {
123
+ int xFloor = (int)Mathf.Floor(x * ratioX);
124
+ float xLerp = x * ratioX - xFloor;
125
+ newColors[yw + x] = ColorLerpUnclamped(
126
+ ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp),
127
+ ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp),
128
+ y * ratioY - yFloor);
129
+ }
130
+ }
131
+
132
+ _ = mutex.WaitOne();
133
+ finishCount++;
134
+ mutex.ReleaseMutex();
135
+ }
136
+
137
+ private static void PointScale(System.Object obj)
138
+ {
139
+ ThreadData threadData = (ThreadData)obj;
140
+ for (int y = threadData.start; y < threadData.end; y++)
141
+ {
142
+ int thisY = (int)(ratioY * y) * w;
143
+ int yw = y * w2;
144
+ for (var x = 0; x < w2; x++)
145
+ {
146
+ newColors[yw + x] = texColors[(int)(thisY + ratioX * x)];
147
+ }
148
+ }
149
+
150
+ _ = mutex.WaitOne();
151
+ finishCount++;
152
+ mutex.ReleaseMutex();
153
+ }
154
+
155
+ private static Color ColorLerpUnclamped(Color c1, Color c2, float value)
156
+ {
157
+ return new Color(
158
+ c1.r + (c2.r - c1.r) * value,
159
+ c1.g + (c2.g - c1.g) * value,
160
+ c1.b + (c2.b - c1.b) * value,
161
+ c1.a + (c2.a - c1.a) * value);
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 985292b70519406bafda70e5ab4e06ce
3
+ timeCreated: 1720667849
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "1.0.1-rc03",
3
+ "version": "1.0.1-rc05",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {