├── .gitignore
├── CacheBehaviour.meta
├── CacheBehaviour
├── CacheBehaviour.cs
├── CacheBehaviour.cs.meta
├── README.md
└── README.md.meta
├── ComponentMenu.png
├── ComponentMenu.png.meta
├── Dispatcher.meta
├── Dispatcher
├── Dispatcher.cs
├── Dispatcher.cs.meta
├── README.md
└── README.md.meta
├── DrawTitleSafeArea.meta
├── DrawTitleSafeArea
├── DrawTitleSafeArea.cs
├── DrawTitleSafeArea.cs.meta
├── Editor.meta
├── Editor
│ ├── DrawTitleSafeAreaEditor.cs
│ └── DrawTitleSafeAreaEditor.cs.meta
├── README.md
└── README.md.meta
├── EditorTools.meta
├── EditorTools
├── HorizontalBlock.cs
├── HorizontalBlock.cs.meta
├── IndentBlock.cs
├── IndentBlock.cs.meta
├── README.md
├── README.md.meta
├── ScrollViewBlock.cs
├── ScrollViewBlock.cs.meta
├── VerticalBlock.cs
└── VerticalBlock.cs.meta
├── ExclusiveChildren.meta
├── ExclusiveChildren
├── Editor.meta
├── Editor
│ ├── ExclusiveChildrenEditor.cs
│ └── ExclusiveChildrenEditor.cs.meta
├── ExclusiveChildren.cs
├── ExclusiveChildren.cs.meta
├── ExclusiveChildrenScreenshot.png
├── ExclusiveChildrenScreenshot.png.meta
├── README.md
└── README.md.meta
├── Future.meta
├── Future
├── Future.cs
├── Future.cs.meta
├── README.md
└── README.md.meta
├── GameSaveSystem.meta
├── GameSaveSystem
├── Demo.meta
├── Demo
│ ├── DemoGameSave.cs
│ ├── DemoGameSave.cs.meta
│ ├── GameSaveSystemDemo.cs
│ ├── GameSaveSystemDemo.cs.meta
│ ├── GameSaveSystemDemo.unity
│ └── GameSaveSystemDemo.unity.meta
├── GameSaveLoadResult.cs
├── GameSaveLoadResult.cs.meta
├── GameSaveSystem.cs
├── GameSaveSystem.cs.meta
├── GameSaveSystemSettings.cs
├── GameSaveSystemSettings.cs.meta
├── IGameSave.cs
├── IGameSave.cs.meta
├── README.md
└── README.md.meta
├── ImmediateWindow.meta
├── ImmediateWindow
├── Editor.meta
├── Editor
│ ├── CodeCompiler.cs
│ ├── CodeCompiler.cs.meta
│ ├── ImmediateWindow.cs
│ └── ImmediateWindow.cs.meta
├── README.md
└── README.md.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── ScriptableObjectUtility.meta
├── ScriptableObjectUtility
├── Editor.meta
├── Editor
│ ├── ScriptableObjectUtility.cs
│ └── ScriptableObjectUtility.cs.meta
├── README.md
└── README.md.meta
├── SimpleSpriteAnimation.meta
├── SimpleSpriteAnimation
├── Editor.meta
├── Editor
│ ├── SpriteFrameAnimationEditor.cs
│ └── SpriteFrameAnimationEditor.cs.meta
├── README.md
├── README.md.meta
├── SpriteFrameAnimation.cs
├── SpriteFrameAnimation.cs.meta
├── SpriteFrameAnimator.cs
└── SpriteFrameAnimator.cs.meta
├── SnapToSurface.meta
├── SnapToSurface
├── Editor.meta
├── Editor
│ ├── SnapToSurface.cs
│ └── SnapToSurface.cs.meta
├── README.md
└── README.md.meta
├── SortingLayer.meta
├── SortingLayer
├── Editor.meta
├── Editor
│ ├── SortingLayerDrawer.cs
│ ├── SortingLayerDrawer.cs.meta
│ ├── SortingLayerExposedEditor.cs
│ ├── SortingLayerExposedEditor.cs.meta
│ ├── SortingLayerHelper.cs
│ └── SortingLayerHelper.cs.meta
├── README.md
├── README.md.meta
├── Readme_SortingLayerAttribute.png
├── Readme_SortingLayerAttribute.png.meta
├── Readme_SortingLayerExposed.png
├── Readme_SortingLayerExposed.png.meta
├── SortingLayerAttribute.cs
├── SortingLayerAttribute.cs.meta
├── SortingLayerExposed.cs
└── SortingLayerExposed.cs.meta
├── TimeScaleIndependentUpdate.meta
├── TimeScaleIndependentUpdate
├── README.md
├── README.md.meta
├── TimeScaleIndependentAnimation.cs
├── TimeScaleIndependentAnimation.cs.meta
├── TimeScaleIndependentParticleSystem.cs
├── TimeScaleIndependentParticleSystem.cs.meta
├── TimeScaleIndependentUpdate.cs
└── TimeScaleIndependentUpdate.cs.meta
├── UnityConstants.meta
├── UnityConstants
├── Editor.meta
├── Editor
│ ├── UnityConstantsGenerator.cs
│ └── UnityConstantsGenerator.cs.meta
├── README.md
└── README.md.meta
├── UnityLock.meta
└── UnityLock
├── Editor.meta
├── Editor
├── UnityLock.cs
├── UnityLock.cs.meta
├── UnityLockHierarchyIcon.png
└── UnityLockHierarchyIcon.png.meta
├── README.md
└── README.md.meta
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 |
--------------------------------------------------------------------------------
/CacheBehaviour.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 16370331fbe4e4b37b08b7f6b38224db
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/CacheBehaviour/CacheBehaviour.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System;
3 |
4 | namespace UnityToolbag
5 | {
6 | ///
7 | /// A safe, drop-in replacement for MonoBehaviour as your base class. Each property value is cached
8 | /// and GetComponent will be called if the instance is null or is destroyed.
9 | ///
10 | public abstract class CacheBehaviour : MonoBehaviour
11 | {
12 | [HideInInspector, NonSerialized]
13 | private Animation _animation;
14 |
15 | ///
16 | /// Gets the Animation attached to the object.
17 | ///
18 | public new Animation animation { get { return _animation ? _animation : (_animation = GetComponent()); } }
19 |
20 | [HideInInspector, NonSerialized]
21 | private AudioSource _audio;
22 |
23 | ///
24 | /// Gets the AudioSource attached to the object.
25 | ///
26 | public new AudioSource audio { get { return _audio ? _audio : (_audio = GetComponent()); } }
27 |
28 | [HideInInspector, NonSerialized]
29 | private Camera _camera;
30 |
31 | ///
32 | /// Gets the Camera attached to the object.
33 | ///
34 | public new Camera camera { get { return _camera ? _camera : (_camera = GetComponent()); } }
35 |
36 | [HideInInspector, NonSerialized]
37 | private Collider _collider;
38 |
39 | ///
40 | /// Gets the Collider attached to the object.
41 | ///
42 | public new Collider collider { get { return _collider ? _collider : (_collider = GetComponent()); } }
43 |
44 | [HideInInspector, NonSerialized]
45 | private Collider2D _collider2D;
46 |
47 | ///
48 | /// Gets the Collider2D attached to the object.
49 | ///
50 | public new Collider2D collider2D { get { return _collider2D ? _collider2D : (_collider2D = GetComponent()); } }
51 |
52 | [HideInInspector, NonSerialized]
53 | private ConstantForce _constantForce;
54 |
55 | ///
56 | /// Gets the ConstantForce attached to the object.
57 | ///
58 | public new ConstantForce constantForce { get { return _constantForce ? _constantForce : (_constantForce = GetComponent()); } }
59 |
60 | [HideInInspector, NonSerialized]
61 | private GUIText _guiText;
62 |
63 | ///
64 | /// Gets the GUIText attached to the object.
65 | ///
66 | public new GUIText guiText { get { return _guiText ? _guiText : (_guiText = GetComponent()); } }
67 |
68 | [HideInInspector, NonSerialized]
69 | private GUITexture _guiTexture;
70 |
71 | ///
72 | /// Gets the GUITexture attached to the object.
73 | ///
74 | public new GUITexture guiTexture { get { return _guiTexture ? _guiTexture : (_guiTexture = GetComponent()); } }
75 |
76 | [HideInInspector, NonSerialized]
77 | private HingeJoint _hingeJoint;
78 |
79 | ///
80 | /// Gets the HingeJoint attached to the object.
81 | ///
82 | public new HingeJoint hingeJoint { get { return _hingeJoint ? _hingeJoint : (_hingeJoint = GetComponent()); } }
83 |
84 | [HideInInspector, NonSerialized]
85 | private Light _light;
86 |
87 | ///
88 | /// Gets the Light attached to the object.
89 | ///
90 | public new Light light { get { return _light ? _light : (_light = GetComponent()); } }
91 |
92 | [HideInInspector, NonSerialized]
93 | private NetworkView _networkView;
94 |
95 | ///
96 | /// Gets the NetworkView attached to the object.
97 | ///
98 | public new NetworkView networkView { get { return _networkView ? _networkView : (_networkView = GetComponent()); } }
99 |
100 | [HideInInspector, NonSerialized]
101 | private ParticleEmitter _particleEmitter;
102 |
103 | ///
104 | /// Gets the ParticleEmitter attached to the object.
105 | ///
106 | public new ParticleEmitter particleEmitter { get { return _particleEmitter ? _particleEmitter : (_particleEmitter = GetComponent()); } }
107 |
108 | [HideInInspector, NonSerialized]
109 | private ParticleSystem _particleSystem;
110 |
111 | ///
112 | /// Gets the ParticleSystem attached to the object.
113 | ///
114 | public new ParticleSystem particleSystem { get { return _particleSystem ? _particleSystem : (_particleSystem = GetComponent()); } }
115 |
116 | [HideInInspector, NonSerialized]
117 | private Renderer _renderer;
118 |
119 | ///
120 | /// Gets the Renderer attached to the object.
121 | ///
122 | public new Renderer renderer { get { return _renderer ? _renderer : (_renderer = GetComponent()); } }
123 |
124 | [HideInInspector, NonSerialized]
125 | private Rigidbody _rigidbody;
126 |
127 | ///
128 | /// Gets the Rigidbody attached to the object.
129 | ///
130 | public new Rigidbody rigidbody { get { return _rigidbody ? _rigidbody : (_rigidbody = GetComponent()); } }
131 |
132 | [HideInInspector, NonSerialized]
133 | private Rigidbody2D _rigidbody2D;
134 |
135 | ///
136 | /// Gets the Rigidbody2D attached to the object.
137 | ///
138 | public new Rigidbody2D rigidbody2D { get { return _rigidbody2D ? _rigidbody2D : (_rigidbody2D = GetComponent()); } }
139 |
140 | [HideInInspector, NonSerialized]
141 | private Transform _transform;
142 |
143 | ///
144 | /// Gets the Transform attached to the object.
145 | ///
146 | public new Transform transform { get { return _transform ? _transform : (_transform = GetComponent()); } }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/CacheBehaviour/CacheBehaviour.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4b3f69b8ea2dc49e9b8d17b1b78f72d4
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/CacheBehaviour/README.md:
--------------------------------------------------------------------------------
1 | CacheBehaviour
2 | ===
3 |
4 | There are two reasons not to use the default component properties (e.g. `rigidBody`, `particleSystem`, etc) on `MonoBehaviour`:
5 |
6 | 1. The properties do no caching, so each time you use the property you are calling `GetComponent`.
7 | 2. Unity 5 is [removing all of them](http://blogs.unity3d.com/2014/06/23/unity5-api-changes-automatic-script-updating/) aside from the `transform` property (which it will start caching).
8 |
9 | `CacheBehaviour` is a drop-in replacement for `MonoBehaviour` that exposes all the same properties as `MonoBehavior`. The properties will check the cache variable and call `GetComponent` anytime the cached value is invalid.
10 |
11 | Note
12 | ===
13 |
14 | There used to be a `FastCacheBehaviour` that used fields and various methods to obtain the values, trying to get better performance. While this did work, in my experience with my current title, this was a giant pain and in cases where you need direct field access you're better off just doing that work yourself. Then you'll have far fewer fields and you won't be calling strange methods. `CacheBehaviour` is still a nice addition in light of 5.0 and for caching things in 4.x today.
15 |
--------------------------------------------------------------------------------
/CacheBehaviour/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4566643fd2fc7444485656536740f682
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/ComponentMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianMo/UnityToolbag/54867ab617825ab10a9844563ce1dacb9fe6c26b/ComponentMenu.png
--------------------------------------------------------------------------------
/ComponentMenu.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1591c4daed8a340d6a3b395c4187f225
3 | TextureImporter:
4 | serializedVersion: 2
5 | mipmaps:
6 | mipMapMode: 0
7 | enableMipMap: 1
8 | linearTexture: 0
9 | correctGamma: 0
10 | fadeOut: 0
11 | borderMipMap: 0
12 | mipMapFadeDistanceStart: 1
13 | mipMapFadeDistanceEnd: 3
14 | bumpmap:
15 | convertToNormalMap: 0
16 | externalNormalMap: 0
17 | heightScale: .25
18 | normalMapFilter: 0
19 | isReadable: 0
20 | grayScaleToAlpha: 0
21 | generateCubemap: 0
22 | seamlessCubemap: 0
23 | textureFormat: -1
24 | maxTextureSize: 1024
25 | textureSettings:
26 | filterMode: -1
27 | aniso: -1
28 | mipBias: -1
29 | wrapMode: -1
30 | nPOTScale: 1
31 | lightmap: 0
32 | compressionQuality: 50
33 | spriteMode: 0
34 | spriteExtrude: 1
35 | spriteMeshType: 1
36 | alignment: 0
37 | spritePivot: {x: .5, y: .5}
38 | spritePixelsToUnits: 100
39 | alphaIsTransparency: 0
40 | textureType: -1
41 | buildTargetSettings: []
42 | spriteSheet:
43 | sprites: []
44 | spritePackingTag:
45 | userData:
46 |
--------------------------------------------------------------------------------
/Dispatcher.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dcb2fc46eb0e545f5997c0d60c3f299f
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/Dispatcher/Dispatcher.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 |
6 | namespace UnityToolbag
7 | {
8 | ///
9 | /// A system for dispatching code to execute on the main thread.
10 | ///
11 | [AddComponentMenu("UnityToolbag/Dispatcher")]
12 | public class Dispatcher : MonoBehaviour
13 | {
14 | private static Dispatcher _instance;
15 |
16 | // We can't use the behaviour reference from other threads, so we use a separate bool
17 | // to track the instance so we can use that on the other threads.
18 | private static bool _instanceExists;
19 |
20 | private static Thread _mainThread;
21 | private static object _lockObject = new object();
22 | private static readonly Queue _actions = new Queue();
23 |
24 | ///
25 | /// Gets a value indicating whether or not the current thread is the game's main thread.
26 | ///
27 | public static bool isMainThread
28 | {
29 | get
30 | {
31 | return Thread.CurrentThread == _mainThread;
32 | }
33 | }
34 |
35 | ///
36 | /// Queues an action to be invoked on the main game thread.
37 | ///
38 | /// The action to be queued.
39 | public static void InvokeAsync(Action action)
40 | {
41 | if (!_instanceExists) {
42 | Debug.LogError("No Dispatcher exists in the scene. Actions will not be invoked!");
43 | return;
44 | }
45 |
46 | if (isMainThread) {
47 | // Don't bother queuing work on the main thread; just execute it.
48 | action();
49 | }
50 | else {
51 | lock (_lockObject) {
52 | _actions.Enqueue(action);
53 | }
54 | }
55 | }
56 |
57 | ///
58 | /// Queues an action to be invoked on the main game thread and blocks the
59 | /// current thread until the action has been executed.
60 | ///
61 | /// The action to be queued.
62 | public static void Invoke(Action action)
63 | {
64 | if (!_instanceExists) {
65 | Debug.LogError("No Dispatcher exists in the scene. Actions will not be invoked!");
66 | return;
67 | }
68 |
69 | bool hasRun = false;
70 |
71 | InvokeAsync(() =>
72 | {
73 | action();
74 | hasRun = true;
75 | });
76 |
77 | // Lock until the action has run
78 | while (!hasRun) {
79 | Thread.Sleep(5);
80 | }
81 | }
82 |
83 | void Awake()
84 | {
85 | if (_instance) {
86 | DestroyImmediate(this);
87 | }
88 | else {
89 | _instance = this;
90 | _instanceExists = true;
91 | _mainThread = Thread.CurrentThread;
92 | }
93 | }
94 |
95 | void OnDestroy()
96 | {
97 | if (_instance == this) {
98 | _instance = null;
99 | _instanceExists = false;
100 | }
101 | }
102 |
103 | void Update()
104 | {
105 | lock (_lockObject) {
106 | while (_actions.Count > 0) {
107 | _actions.Dequeue()();
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Dispatcher/Dispatcher.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d041b0f345cea4dc4b7405f0d7c99b31
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/Dispatcher/README.md:
--------------------------------------------------------------------------------
1 | Dispatcher
2 | ===
3 |
4 | `Dispatcher` is an incredibly simple little component/system that makes it possible to invoke code on the main thread from other threads. Usage is simple:
5 |
6 | 1. Add the `Dispatcher` component to an object in your scene.
7 | 2. Call the static methods `InvokeAsync(Action)` or `Invoke(Action)` to run actions on the main thread.
8 |
9 | `InvokeAsync` will queue up the action and return immediately so your thread can keep on going. `Invoke`, on the other hand, will block the calling thread until the action has run. In both cases the `Dispatcher` will immediately invoke the action if the calling thread is the main thread (this also prevents deadlocks in case you call `Invoke` from the main thread).
10 |
11 | `InvokeAsync` will not generate any garbage because it simply queues up the action. `Invoke` currently allocates a new lambda that will invoke the given argument and set a boolean to know when to return from the method. This could be fixed later, but most places are likely fine using `InvokeAsync`.
12 |
--------------------------------------------------------------------------------
/Dispatcher/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0bbd971e3223546fe8ddb538f441815d
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ded9d82dadd21443d975e14987319137
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/DrawTitleSafeArea.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace UnityToolbag
4 | {
5 | public enum TitleSafeSizeMode
6 | {
7 | Pixels,
8 | Percentage
9 | }
10 |
11 | [ExecuteInEditMode]
12 | [AddComponentMenu("UnityToolbag/Draw Title Safe Area")]
13 | public class DrawTitleSafeArea : MonoBehaviour
14 | {
15 | #if UNITY_EDITOR
16 | private Texture2D _blank;
17 | #endif
18 |
19 | [SerializeField]
20 | private Color _innerColor = new Color(1, 1, 0, 0.15f);
21 |
22 | [SerializeField]
23 | private Color _outerColor = new Color(1, 0, 0, 0.15f);
24 |
25 | [SerializeField]
26 | private TitleSafeSizeMode _sizeMode = TitleSafeSizeMode.Percentage;
27 |
28 | [SerializeField]
29 | private int _sizeX = 5;
30 |
31 | [SerializeField]
32 | private int _sizeY = 5;
33 |
34 | #if UNITY_EDITOR
35 | void OnValidate()
36 | {
37 | if (_sizeX < 0) {
38 | _sizeX = 0;
39 | }
40 |
41 | if (_sizeY < 0) {
42 | _sizeY = 0;
43 | }
44 |
45 | if (_sizeMode == TitleSafeSizeMode.Percentage) {
46 | if (_sizeX > 25) {
47 | _sizeX = 25;
48 | }
49 |
50 | if (_sizeY > 25) {
51 | _sizeY = 25;
52 | }
53 | }
54 | }
55 |
56 | void OnDestroy()
57 | {
58 | if (_blank)
59 | {
60 | DestroyImmediate(_blank);
61 | _blank = null;
62 | }
63 | }
64 |
65 | void OnGUI()
66 | {
67 | var camera = GetComponent();
68 | if (!camera) {
69 | return;
70 | }
71 |
72 | if (!_blank) {
73 | _blank = new Texture2D(1, 1);
74 | _blank.SetPixel(0, 0, Color.white);
75 | _blank.hideFlags = HideFlags.HideAndDontSave;
76 | }
77 |
78 | float w = camera.pixelWidth;
79 | float h = camera.pixelHeight;
80 |
81 | // Compute the actual sizes based on the size mode and our sizes
82 | float wMargin = 0;
83 | float hMargin = 0;
84 | switch (_sizeMode) {
85 | case TitleSafeSizeMode.Percentage: {
86 | wMargin = w * (_sizeX / 100.0f);
87 | hMargin = h * (_sizeY / 100.0f);
88 | break;
89 | }
90 | case TitleSafeSizeMode.Pixels: {
91 | // Clamp to 1/4 the screen size so we never overlap the other side
92 | wMargin = Mathf.Clamp(_sizeX, 0, w / 4);
93 | hMargin = Mathf.Clamp(_sizeY, 0, h / 4);
94 | break;
95 | }
96 | }
97 |
98 | // Draw the outer region first
99 | GUI.color = _outerColor;
100 | GUI.DrawTexture(new Rect(0, 0, w, hMargin), _blank, ScaleMode.StretchToFill, true);
101 | GUI.DrawTexture(new Rect(0, h - hMargin, w, hMargin), _blank, ScaleMode.StretchToFill, true);
102 | GUI.DrawTexture(new Rect(0, hMargin, wMargin, h - hMargin * 2), _blank, ScaleMode.StretchToFill, true);
103 | GUI.DrawTexture(new Rect(w - wMargin, hMargin, wMargin, h - hMargin * 2), _blank, ScaleMode.StretchToFill, true);
104 |
105 | // Then the inner region
106 | GUI.color = _innerColor;
107 | GUI.DrawTexture(new Rect(wMargin, hMargin, w - wMargin * 2, hMargin), _blank, ScaleMode.StretchToFill, true);
108 | GUI.DrawTexture(new Rect(wMargin, h - hMargin * 2, w - wMargin * 2, hMargin), _blank, ScaleMode.StretchToFill, true);
109 | GUI.DrawTexture(new Rect(wMargin, hMargin * 2, wMargin, h - hMargin * 4), _blank, ScaleMode.StretchToFill, true);
110 | GUI.DrawTexture(new Rect(w - wMargin * 2, hMargin * 2, wMargin, h - hMargin * 4), _blank, ScaleMode.StretchToFill, true);
111 | }
112 | #endif // UNITY_EDITOR
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/DrawTitleSafeArea.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 66b96b93e882e4e0da265ed123880615
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9c34b3378ccfa48c2a3f8adf19ca8c39
3 | folderAsset: yes
4 | timeCreated: 1427125397
5 | licenseType: Free
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/Editor/DrawTitleSafeAreaEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 |
4 | namespace UnityToolbag
5 | {
6 | [CustomEditor(typeof(DrawTitleSafeArea))]
7 | public class DrawTitleSafeAreaEditor : Editor
8 | {
9 | public override void OnInspectorGUI()
10 | {
11 | serializedObject.Update();
12 |
13 | EditorGUILayout.PropertyField(serializedObject.FindProperty("_innerColor"));
14 | EditorGUILayout.PropertyField(serializedObject.FindProperty("_outerColor"));
15 |
16 | var sizeMode = serializedObject.FindProperty("_sizeMode");
17 | EditorGUILayout.PropertyField(sizeMode);
18 |
19 | if (sizeMode.intValue == (int)TitleSafeSizeMode.Percentage) {
20 | var sizeX = serializedObject.FindProperty("_sizeX");
21 | sizeX.intValue = EditorGUILayout.IntSlider("Size X", sizeX.intValue, 0, 25);
22 |
23 | var sizeY = serializedObject.FindProperty("_sizeY");
24 | sizeY.intValue = EditorGUILayout.IntSlider("Size Y", sizeY.intValue, 0, 25);
25 | }
26 | else {
27 | EditorGUILayout.PropertyField(serializedObject.FindProperty("_sizeX"));
28 | EditorGUILayout.PropertyField(serializedObject.FindProperty("_sizeY"));
29 | }
30 |
31 | serializedObject.ApplyModifiedProperties();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/Editor/DrawTitleSafeAreaEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 432609760845c4637b75966e1ac824e6
3 | timeCreated: 1427125404
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/README.md:
--------------------------------------------------------------------------------
1 | Draw Title Safe Area
2 | ===
3 |
4 | This script helps debug games for TVs (i.e. Ouya or other console) by rendering the title safe area guides on top of the scene. It draws a red border in the outside 10% of the screen and a yellow border in the outside 10-20%. All your game's action should be inside the red border and all your game's vital UI and text should be inside the yellow border.
5 |
6 | The opacity of the border is adjustable so you can make it work for your needs and of course you can dynamically toggle the component on and off as needed.
7 |
--------------------------------------------------------------------------------
/DrawTitleSafeArea/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 76101c4a40834435b914d9d3a892bf39
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/EditorTools.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 69ea53114a72e4751b26180254a3cb27
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/EditorTools/HorizontalBlock.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System;
4 |
5 | namespace UnityToolbag
6 | {
7 | public class HorizontalBlock : IDisposable
8 | {
9 | public HorizontalBlock(params GUILayoutOption[] options)
10 | {
11 | GUILayout.BeginHorizontal(options);
12 | }
13 |
14 | public HorizontalBlock(GUIStyle style, params GUILayoutOption[] options)
15 | {
16 | GUILayout.BeginHorizontal(style, options);
17 | }
18 |
19 | public void Dispose()
20 | {
21 | GUILayout.EndHorizontal();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/EditorTools/HorizontalBlock.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 38948ce16fdf34bf08126f2c24692f51
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/EditorTools/IndentBlock.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using System;
3 |
4 | namespace UnityToolbag
5 | {
6 | public class IndentBlock : IDisposable
7 | {
8 | public IndentBlock()
9 | {
10 | EditorGUI.indentLevel++;
11 | }
12 |
13 | public void Dispose()
14 | {
15 | EditorGUI.indentLevel--;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/EditorTools/IndentBlock.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 695972dbcbf1646aca9bb8f352524337
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/EditorTools/README.md:
--------------------------------------------------------------------------------
1 | Editor Tools
2 | ===
3 |
4 | These are just some little helper tools for making editor GUI layout a bit easier. Largely utilizing the IDisposable pattern to increase readability and safety when making UI calls that require a Begin/End pair. These add a lot of safety (can't forget the End call), simplify the usage a little bit, and increase readability by creating a new scope and indent level in your code, making it easier to see what UI is inside your different blocks.
5 |
6 | **IndentBlock**
7 |
8 | Easily create a section of indented UI.
9 |
10 | EditorGUILayout.LabelField("Some Indented Stuff:");
11 | using (new IndentBlock())
12 | {
13 | EditorGUILayout.LabelField("This is indented!");
14 | }
15 |
16 | **HorizontalBlock**
17 |
18 | Create a horizontal section of layout.
19 |
20 | using (new HorizontalBlock())
21 | {
22 | if (GUILayout.Button("Left Button")) { }
23 | if (GUILayout.Button("Right Button")) { }
24 | }
25 |
26 | **VerticalBlock**
27 |
28 | Create a vertical section of layout.
29 |
30 | using (new VerticalBlock())
31 | {
32 | if (GUILayout.Button("Top Button")) { }
33 | if (GUILayout.Button("Bottom Button")) { }
34 | }
35 |
36 | **ScrollViewBlock**
37 |
38 | Creates a scroll view section. Requires a ref parameter for the scroll location that it will update.
39 |
40 | // In your editor class, define a private variable
41 | private Vector2 _scrollViewPosition;
42 |
43 | // In your GUI code you can easily make scroll views
44 | using (new ScrollViewBlock(ref _scrollViewPosition))
45 | {
46 | for (int i = 0; i < 20; i++)
47 | {
48 | if (GUILayout.Button("Button " + i)) { }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/EditorTools/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 46291061369b64e6da4198a6aa13400a
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/EditorTools/ScrollViewBlock.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System;
4 |
5 | namespace UnityToolbag
6 | {
7 | public class ScrollViewBlock : IDisposable
8 | {
9 | public ScrollViewBlock(ref Vector2 scrollPosition, params GUILayoutOption[] options)
10 | {
11 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, options);
12 | }
13 |
14 | public void Dispose()
15 | {
16 | EditorGUILayout.EndScrollView();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/EditorTools/ScrollViewBlock.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e63fdf01242794d7aaf59bdf72a6b99e
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/EditorTools/VerticalBlock.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System;
4 |
5 | namespace UnityToolbag
6 | {
7 | public class VerticalBlock : IDisposable
8 | {
9 | public VerticalBlock(params GUILayoutOption[] options)
10 | {
11 | GUILayout.BeginVertical(options);
12 | }
13 |
14 | public VerticalBlock(GUIStyle style, params GUILayoutOption[] options)
15 | {
16 | GUILayout.BeginVertical(style, options);
17 | }
18 |
19 | public void Dispose()
20 | {
21 | GUILayout.EndVertical();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/EditorTools/VerticalBlock.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 187c896238b5248e582ef61f7d97224d
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ExclusiveChildren.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6f6bac55d5bb247a0a6914f64fc70989
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ExclusiveChildren/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6a7b068de79c24002b10d1e344d61ea0
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ExclusiveChildren/Editor/ExclusiveChildrenEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System.Linq;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 |
7 | namespace UnityToolbag
8 | {
9 | [CustomEditor(typeof(ExclusiveChildren))]
10 | public class ExclusiveChildrenEditor : Editor
11 | {
12 | public override void OnInspectorGUI()
13 | {
14 | // Get the transform of the object and use that to get all children in alphabetical order
15 | var transform = (target as ExclusiveChildren).transform;
16 | var children = (transform as IEnumerable).Cast().OrderBy(t => t.gameObject.name).ToArray();
17 |
18 | // Make a button for each child. Pressing the button enables that child and disables all others
19 | for (int i = 0; i < children.Length; i++) {
20 | var child = children[i];
21 | if (GUILayout.Button(child.gameObject.name)) {
22 | foreach (var other in children) {
23 | other.gameObject.SetActive(child == other);
24 | }
25 | break;
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ExclusiveChildren/Editor/ExclusiveChildrenEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 204f45eeee49e44cbaf4f5e120b4383f
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ExclusiveChildren/ExclusiveChildren.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace UnityToolbag
4 | {
5 | // Component does nothing; editor script does all the magic
6 | [AddComponentMenu("UnityToolbag/Exclusive Children")]
7 | public class ExclusiveChildren : MonoBehaviour
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ExclusiveChildren/ExclusiveChildren.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 76263c573590443ab80bf929d2be397c
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ExclusiveChildren/ExclusiveChildrenScreenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianMo/UnityToolbag/54867ab617825ab10a9844563ce1dacb9fe6c26b/ExclusiveChildren/ExclusiveChildrenScreenshot.png
--------------------------------------------------------------------------------
/ExclusiveChildren/ExclusiveChildrenScreenshot.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 974c4118104f549a28c5e54a442f6aae
3 | TextureImporter:
4 | serializedVersion: 2
5 | mipmaps:
6 | mipMapMode: 0
7 | enableMipMap: 1
8 | linearTexture: 0
9 | correctGamma: 0
10 | fadeOut: 0
11 | borderMipMap: 0
12 | mipMapFadeDistanceStart: 1
13 | mipMapFadeDistanceEnd: 3
14 | bumpmap:
15 | convertToNormalMap: 0
16 | externalNormalMap: 0
17 | heightScale: .25
18 | normalMapFilter: 0
19 | isReadable: 0
20 | grayScaleToAlpha: 0
21 | generateCubemap: 0
22 | seamlessCubemap: 0
23 | textureFormat: -1
24 | maxTextureSize: 1024
25 | textureSettings:
26 | filterMode: -1
27 | aniso: -1
28 | mipBias: -1
29 | wrapMode: -1
30 | nPOTScale: 1
31 | lightmap: 0
32 | compressionQuality: 50
33 | spriteMode: 0
34 | spriteExtrude: 1
35 | spriteMeshType: 1
36 | alignment: 0
37 | spritePivot: {x: .5, y: .5}
38 | spritePixelsToUnits: 100
39 | alphaIsTransparency: 0
40 | textureType: -1
41 | buildTargetSettings: []
42 | spriteSheet:
43 | sprites: []
44 | spritePackingTag:
45 | userData:
46 |
--------------------------------------------------------------------------------
/ExclusiveChildren/README.md:
--------------------------------------------------------------------------------
1 | Exclusive Children
2 | ===
3 |
4 | This simple component/editor pair allows you to easily work with game objects that are exclusive views. A couple places this might be useful are:
5 |
6 | - On a parent game object that contains multiple menus for your main menu
7 | - On an in-game UI game object that contains your different UI panes
8 |
9 | The script is primarily just an editor tool. The component will display a set of buttons, one for each child of the object. Clicking any button will enable that object and disable all others.
10 |
11 | 
12 |
--------------------------------------------------------------------------------
/ExclusiveChildren/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ce25e91f426c04929a623a03b6379513
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/Future.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e157ae705db5d47a69f8d43d388ed814
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/Future/Future.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 |
5 | namespace UnityToolbag
6 | {
7 | ///
8 | /// Describes the state of a future.
9 | ///
10 | public enum FutureState
11 | {
12 | ///
13 | /// The future hasn't begun to resolve a value.
14 | ///
15 | Pending,
16 |
17 | ///
18 | /// The future is working on resolving a value.
19 | ///
20 | Processing,
21 |
22 | ///
23 | /// The future has a value ready.
24 | ///
25 | Success,
26 |
27 | ///
28 | /// The future failed to resolve a value.
29 | ///
30 | Error
31 | }
32 |
33 | ///
34 | /// Defines the interface of an object that can be used to track a future value.
35 | ///
36 | /// The type of object being retrieved.
37 | public interface IFuture
38 | {
39 | ///
40 | /// Gets the state of the future.
41 | ///
42 | FutureState state { get; }
43 |
44 | ///
45 | /// Gets the value if the State is Success.
46 | ///
47 | T value { get; }
48 |
49 | ///
50 | /// Gets the failure exception if the State is Error.
51 | ///
52 | Exception error { get; }
53 |
54 | ///
55 | /// Adds a new callback to invoke if the future value is retrieved successfully.
56 | ///
57 | /// The callback to invoke.
58 | /// The future so additional calls can be chained together.
59 | IFuture OnSuccess(FutureCallback callback);
60 |
61 | ///
62 | /// Adds a new callback to invoke if the future has an error.
63 | ///
64 | /// The callback to invoke.
65 | /// The future so additional calls can be chained together.
66 | IFuture OnError(FutureCallback callback);
67 |
68 | ///
69 | /// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
70 | ///
71 | /// The callback to invoke.
72 | /// The future so additional calls can be chained together.
73 | IFuture OnComplete(FutureCallback callback);
74 | }
75 |
76 | ///
77 | /// Defines the signature for callbacks used by the future.
78 | ///
79 | /// The future.
80 | public delegate void FutureCallback(IFuture future);
81 |
82 | ///
83 | /// An implementation of that can be used internally by methods that return futures.
84 | ///
85 | ///
86 | /// Methods should always return the interface when calling code requests a future.
87 | /// This class is intended to be constructed internally in the method to provide a simple implementation of
88 | /// the interface. By returning the interface instead of the class it ensures the implementation can change
89 | /// later on if requirements change, without affecting the calling code.
90 | ///
91 | /// The type of object being retrieved.
92 | public sealed class Future : IFuture
93 | {
94 | private volatile FutureState _state;
95 | private T _value;
96 | private Exception _error;
97 |
98 | private readonly List> _successCallbacks = new List>();
99 | private readonly List> _errorCallbacks = new List>();
100 |
101 | ///
102 | /// Gets the state of the future.
103 | ///
104 | public FutureState state { get { return _state; } }
105 |
106 | ///
107 | /// Gets the value if the State is Success.
108 | ///
109 | public T value
110 | {
111 | get
112 | {
113 | if (_state != FutureState.Success) {
114 | throw new InvalidOperationException("value is not available unless state is Success.");
115 | }
116 |
117 | return _value;
118 | }
119 | }
120 |
121 | ///
122 | /// Gets the failure exception if the State is Error.
123 | ///
124 | public Exception error
125 | {
126 | get
127 | {
128 | if (_state != FutureState.Error) {
129 | throw new InvalidOperationException("error is not available unless state is Error.");
130 | }
131 |
132 | return _error;
133 | }
134 | }
135 |
136 | ///
137 | /// Initializes a new instance of the class.
138 | ///
139 | public Future()
140 | {
141 | _state = FutureState.Pending;
142 | }
143 |
144 | ///
145 | /// Adds a new callback to invoke if the future value is retrieved successfully.
146 | ///
147 | /// The callback to invoke.
148 | /// The future so additional calls can be chained together.
149 | public IFuture OnSuccess(FutureCallback callback)
150 | {
151 | if (_state == FutureState.Success) {
152 | if (Dispatcher.isMainThread) {
153 | callback(this);
154 | }
155 | else {
156 | Dispatcher.InvokeAsync(() => callback(this));
157 | }
158 | }
159 | else if (_state != FutureState.Error && !_successCallbacks.Contains(callback)) {
160 | _successCallbacks.Add(callback);
161 | }
162 |
163 | return this;
164 | }
165 |
166 | ///
167 | /// Adds a new callback to invoke if the future has an error.
168 | ///
169 | /// The callback to invoke.
170 | /// The future so additional calls can be chained together.
171 | public IFuture OnError(FutureCallback callback)
172 | {
173 | if (_state == FutureState.Error) {
174 | if (Dispatcher.isMainThread) {
175 | callback(this);
176 | }
177 | else {
178 | Dispatcher.InvokeAsync(() => callback(this));
179 | }
180 | }
181 | else if (_state != FutureState.Success && !_errorCallbacks.Contains(callback)) {
182 | _errorCallbacks.Add(callback);
183 | }
184 |
185 | return this;
186 | }
187 |
188 | ///
189 | /// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
190 | ///
191 | /// The callback to invoke.
192 | /// The future so additional calls can be chained together.
193 | public IFuture OnComplete(FutureCallback callback)
194 | {
195 | if (_state == FutureState.Success || _state == FutureState.Error) {
196 | if (Dispatcher.isMainThread) {
197 | callback(this);
198 | }
199 | else {
200 | Dispatcher.InvokeAsync(() => callback(this));
201 | }
202 | }
203 | else {
204 | if (!_successCallbacks.Contains(callback)) {
205 | _successCallbacks.Add(callback);
206 | }
207 | if (!_errorCallbacks.Contains(callback)) {
208 | _errorCallbacks.Add(callback);
209 | }
210 | }
211 |
212 | return this;
213 | }
214 |
215 | ///
216 | /// Begins running a given function on a background thread to resolve the future's value, as long
217 | /// as it is still in the Pending state.
218 | ///
219 | /// The function that will retrieve the desired value.
220 | public IFuture Process(Func func)
221 | {
222 | if (_state != FutureState.Pending) {
223 | throw new InvalidOperationException("Cannot process a future that isn't in the Pending state.");
224 | }
225 |
226 | _state = FutureState.Processing;
227 |
228 | ThreadPool.QueueUserWorkItem(_ =>
229 | {
230 | try {
231 | // Directly call the Impl version to avoid the state validation of the public method
232 | AssignImpl(func());
233 | }
234 | catch (Exception e) {
235 | // Directly call the Impl version to avoid the state validation of the public method
236 | FailImpl(e);
237 | }
238 | });
239 |
240 | return this;
241 | }
242 |
243 | ///
244 | /// Allows manually assigning a value to a future, as long as it is still in the pending state.
245 | ///
246 | ///
247 | /// There are times where you may not need to do background processing for a value. For example,
248 | /// you may have a cache of values and can just hand one out. In those cases you still want to
249 | /// return a future for the method signature, but can just call this method to fill in the future.
250 | ///
251 | /// The value to assign the future.
252 | public void Assign(T value)
253 | {
254 | if (_state != FutureState.Pending) {
255 | throw new InvalidOperationException("Cannot assign a value to a future that isn't in the Pending state.");
256 | }
257 |
258 | AssignImpl(value);
259 | }
260 |
261 | ///
262 | /// Allows manually failing a future, as long as it is still in the pending state.
263 | ///
264 | ///
265 | /// As with the Assign method, there are times where you may know a future value is a failure without
266 | /// doing any background work. In those cases you can simply fail the future manually and return it.
267 | ///
268 | /// The exception to use to fail the future.
269 | public void Fail(Exception error)
270 | {
271 | if (_state != FutureState.Pending) {
272 | throw new InvalidOperationException("Cannot fail future that isn't in the Pending state.");
273 | }
274 |
275 | FailImpl(error);
276 | }
277 |
278 | private void AssignImpl(T value)
279 | {
280 | _value = value;
281 | _error = null;
282 | _state = FutureState.Success;
283 |
284 | Dispatcher.InvokeAsync(FlushSuccessCallbacks);
285 | }
286 |
287 | private void FailImpl(Exception error)
288 | {
289 | _value = default(T);
290 | _error = error;
291 | _state = FutureState.Error;
292 |
293 | Dispatcher.InvokeAsync(FlushErrorCallbacks);
294 | }
295 |
296 | private void FlushSuccessCallbacks()
297 | {
298 | foreach (var callback in _successCallbacks) {
299 | callback(this);
300 | }
301 |
302 | _successCallbacks.Clear();
303 | _errorCallbacks.Clear();
304 | }
305 |
306 | private void FlushErrorCallbacks()
307 | {
308 | foreach (var callback in _errorCallbacks) {
309 | callback(this);
310 | }
311 |
312 | _successCallbacks.Clear();
313 | _errorCallbacks.Clear();
314 | }
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/Future/Future.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8433e571a9dab4e1d88c02acb2460ea1
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/Future/README.md:
--------------------------------------------------------------------------------
1 | Future
2 | ===
3 |
4 | Futures are a common pattern in many software projects and you can read more at [http://en.wikipedia.org/wiki/Futures_and_promises](http://en.wikipedia.org/wiki/Futures_and_promises). Typically in C# you'd use the [System.Threading.Tasks](http://msdn.microsoft.com/en-us/library/vstudio/system.threading.tasks(v=vs.110).aspx) namespace, but Unity is using a woefully out of date version of Mono that lacks this functionality. `Future.cs` provides an extremely simple implementation that works for basic use cases.
5 |
6 | The core of the system for game code is the `IFuture` interface. The interface provides the user with the state of the future, the value (if one was retrieved), an exception (if an error occurred while retrieving the value), and the ability to register callbacks for completion.
7 |
8 | The file also provides an implementation of `IFuture` in `Future`. Game code that is _consuming_ futures should always use the interface. Game code that is _creating_ futures can either implement the interface themselves or use the included implementation. The included implementation provides a `Process(Func)` method that simply uses the `ThreadPool` class to execute the logic.
9 |
10 | The `Future` implementation of using delegates is not generally going to be garbage free, however the futures are generally used for things like web resources or game saves which almost always require some allocation of memory to function (such as creating stream writers or readers).
11 |
12 | Additionally `Future` does depend on the [Dispatcher](https://github.com/nickgravelyn/UnityToolbag/tree/master/Dispatcher) class from UnityToolbag (to ensure callbacks are invoked on the main game thread), so you must either include that class in your project or remove/change the `Future` implementation.
13 |
--------------------------------------------------------------------------------
/Future/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b6cad5651345f4c9f86f041c28fcd9b7
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/GameSaveSystem.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 42183c2753acc490e84e8f46f9785bb9
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1fc1c50bb0fc449a99b0db8e56a394a4
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/DemoGameSave.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 |
5 | namespace UnityToolbag
6 | {
7 | // Just a basic implementation of IGameSave
8 | public class DemoGameSave : IGameSave
9 | {
10 | // Helper variables to test failing when saving and loading
11 | public static bool FailNextSave = false;
12 | public static bool FailNextLoad = false;
13 |
14 | public string text = "Hello, world!";
15 |
16 | public void Reset()
17 | {
18 | text = string.Empty;
19 | }
20 |
21 | public void Save(Stream stream)
22 | {
23 | if (FailNextSave) {
24 | FailNextSave = false;
25 | throw new InvalidOperationException("Intentionally failing the save.");
26 | }
27 |
28 | using (var writer = new StreamWriter(stream)) {
29 | // Write the date into the file just to prove the backup system is working as intended.
30 | writer.WriteLine(DateTime.Now.ToString());
31 | writer.WriteLine(text);
32 | }
33 |
34 | // Pretend this is taking a long time ;)
35 | Thread.Sleep(1000);
36 | }
37 |
38 | public void Load(Stream stream)
39 | {
40 | if (FailNextLoad) {
41 | FailNextLoad = false;
42 | throw new InvalidOperationException("Intentionally failing the load.");
43 | }
44 |
45 | using (var reader = new StreamReader(stream)) {
46 | // Read the line containing the date; we don't really need it, though.
47 | reader.ReadLine();
48 | text = reader.ReadLine();
49 | }
50 |
51 | // Pretend this is taking a long time ;)
52 | Thread.Sleep(1000);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/DemoGameSave.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 954a0a42dddbf4c94a652c853815037c
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/GameSaveSystemDemo.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System;
3 | using System.Collections;
4 |
5 | namespace UnityToolbag
6 | {
7 | // A basic demonstration of using the game save system.
8 | public class GameSaveSystemDemo : MonoBehaviour
9 | {
10 | private DemoGameSave _gameSave;
11 | private bool _isLoading, _isSaving;
12 | private int _dotCount = 0;
13 |
14 | void Start()
15 | {
16 | // Games have to Initialize the system before using it.
17 | GameSaveSystem.Initialize(new GameSaveSystemSettings
18 | {
19 | companyName = "Test Company",
20 | gameName = "Test Game",
21 | useRollingBackups = true,
22 | backupCount = 2
23 | });
24 |
25 | // Log the output folder where the saves will be
26 | Debug.Log("Save location: " + GameSaveSystem.saveLocation, this);
27 |
28 | // Use a coroutine to update the animated dots for our text
29 | StartCoroutine(UpdateDots());
30 | }
31 |
32 | IEnumerator UpdateDots()
33 | {
34 | while (true) {
35 | yield return new WaitForSeconds(0.25f);
36 | _dotCount = (_dotCount + 1) % 4;
37 | }
38 | }
39 |
40 | void DemoLoad(bool forceFromDisk)
41 | {
42 | // Don't do more than one load/save at a time
43 | if (!_isLoading && !_isSaving) {
44 | _isLoading = true;
45 |
46 | // Load the save and handle the results.
47 | GameSaveSystem.Load("demosave", forceFromDisk)
48 | .OnSuccess(f =>
49 | {
50 | if (f.value.usedBackupFile) {
51 | Debug.LogWarning("Load successful, but a backup file was used.", this);
52 | }
53 | else {
54 | Debug.Log("Load successful. Was cached? " + f.value.wasCached, this);
55 | }
56 |
57 | _gameSave = f.value.save;
58 | })
59 | .OnError(f =>
60 | {
61 | Debug.LogWarning("Load failed: " + f.error.Message + ". Creating new game save for demo.", this);
62 | _gameSave = new DemoGameSave();
63 | })
64 | .OnComplete(f => _isLoading = false);
65 | }
66 | }
67 |
68 | void DemoSave()
69 | {
70 | // Don't do more than one load/save at a time
71 | if (!_isLoading && !_isSaving && _gameSave != null) {
72 | _isSaving = true;
73 |
74 | // Save our game save and handle the result
75 | GameSaveSystem.Save("demosave", _gameSave)
76 | .OnSuccess(f => Debug.Log("Save successful.", this))
77 | .OnError(f => Debug.LogWarning("Save failed: " + f.error.Message, this))
78 | .OnComplete(f => _isSaving = false);
79 | }
80 | }
81 |
82 | void OnGUI()
83 | {
84 | // Show animated status if loading or saving.
85 | if (_isLoading) {
86 | GUILayout.Label("Loading" + new string('.', _dotCount));
87 | }
88 | else if (_isSaving) {
89 | GUILayout.Label("Saving" + new string('.', _dotCount));
90 | }
91 | else {
92 | // If we have a save, show the text from it and add an option to change the text
93 | if (_gameSave != null) {
94 | GUILayout.Label("Game save text: " + _gameSave.text);
95 |
96 | if (GUILayout.Button("Change demo save text")) {
97 | _gameSave.text = Guid.NewGuid().ToString();
98 | }
99 | }
100 |
101 | // Show some buttons for playing with the save
102 | if (GUILayout.Button("New Save")) {
103 | _gameSave = new DemoGameSave();
104 | }
105 | if (GUILayout.Button("Load")) {
106 | DemoLoad(false);
107 | }
108 | if (GUILayout.Button("Force Load")) {
109 | DemoLoad(true);
110 | }
111 | if (GUILayout.Button("Load and Fail")) {
112 | DemoGameSave.FailNextLoad = true;
113 | DemoLoad(true);
114 | }
115 | if (GUILayout.Button("Save")) {
116 | DemoSave();
117 | }
118 | if (GUILayout.Button("Save and Fail")) {
119 | DemoGameSave.FailNextSave = true;
120 | DemoSave();
121 | }
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/GameSaveSystemDemo.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4104e842946ce43e88ccbe29c61476fd
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/GameSaveSystemDemo.unity:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!29 &1
4 | SceneSettings:
5 | m_ObjectHideFlags: 0
6 | m_PVSData:
7 | m_PVSObjectsArray: []
8 | m_PVSPortalsArray: []
9 | m_OcclusionBakeSettings:
10 | smallestOccluder: 5
11 | smallestHole: .25
12 | backfaceThreshold: 100
13 | --- !u!104 &2
14 | RenderSettings:
15 | m_Fog: 0
16 | m_FogColor: {r: .5, g: .5, b: .5, a: 1}
17 | m_FogMode: 3
18 | m_FogDensity: .00999999978
19 | m_LinearFogStart: 0
20 | m_LinearFogEnd: 300
21 | m_AmbientLight: {r: .200000003, g: .200000003, b: .200000003, a: 1}
22 | m_SkyboxMaterial: {fileID: 0}
23 | m_HaloStrength: .5
24 | m_FlareStrength: 1
25 | m_FlareFadeSpeed: 3
26 | m_HaloTexture: {fileID: 0}
27 | m_SpotCookie: {fileID: 0}
28 | m_ObjectHideFlags: 0
29 | --- !u!127 &3
30 | LevelGameManager:
31 | m_ObjectHideFlags: 0
32 | --- !u!157 &4
33 | LightmapSettings:
34 | m_ObjectHideFlags: 0
35 | m_LightProbes: {fileID: 0}
36 | m_Lightmaps: []
37 | m_LightmapsMode: 1
38 | m_BakedColorSpace: 0
39 | m_UseDualLightmapsInForward: 0
40 | m_LightmapEditorSettings:
41 | m_Resolution: 50
42 | m_LastUsedResolution: 0
43 | m_TextureWidth: 1024
44 | m_TextureHeight: 1024
45 | m_BounceBoost: 1
46 | m_BounceIntensity: 1
47 | m_SkyLightColor: {r: .860000014, g: .930000007, b: 1, a: 1}
48 | m_SkyLightIntensity: 0
49 | m_Quality: 0
50 | m_Bounces: 1
51 | m_FinalGatherRays: 1000
52 | m_FinalGatherContrastThreshold: .0500000007
53 | m_FinalGatherGradientThreshold: 0
54 | m_FinalGatherInterpolationPoints: 15
55 | m_AOAmount: 0
56 | m_AOMaxDistance: .100000001
57 | m_AOContrast: 1
58 | m_LODSurfaceMappingDistance: 1
59 | m_Padding: 0
60 | m_TextureCompression: 0
61 | m_LockAtlas: 0
62 | --- !u!196 &5
63 | NavMeshSettings:
64 | m_ObjectHideFlags: 0
65 | m_BuildSettings:
66 | agentRadius: .5
67 | agentHeight: 2
68 | agentSlope: 45
69 | agentClimb: .400000006
70 | ledgeDropHeight: 0
71 | maxJumpAcrossDistance: 0
72 | accuratePlacement: 0
73 | minRegionArea: 2
74 | widthInaccuracy: 16.666666
75 | heightInaccuracy: 10
76 | m_NavMesh: {fileID: 0}
77 | --- !u!1 &837332104
78 | GameObject:
79 | m_ObjectHideFlags: 0
80 | m_PrefabParentObject: {fileID: 0}
81 | m_PrefabInternal: {fileID: 0}
82 | serializedVersion: 4
83 | m_Component:
84 | - 4: {fileID: 837332106}
85 | - 114: {fileID: 837332107}
86 | - 114: {fileID: 837332105}
87 | m_Layer: 0
88 | m_Name: Demo
89 | m_TagString: Untagged
90 | m_Icon: {fileID: 0}
91 | m_NavMeshLayer: 0
92 | m_StaticEditorFlags: 0
93 | m_IsActive: 1
94 | --- !u!114 &837332105
95 | MonoBehaviour:
96 | m_ObjectHideFlags: 0
97 | m_PrefabParentObject: {fileID: 0}
98 | m_PrefabInternal: {fileID: 0}
99 | m_GameObject: {fileID: 837332104}
100 | m_Enabled: 1
101 | m_EditorHideFlags: 0
102 | m_Script: {fileID: 11500000, guid: 4104e842946ce43e88ccbe29c61476fd, type: 3}
103 | m_Name:
104 | m_EditorClassIdentifier:
105 | --- !u!4 &837332106
106 | Transform:
107 | m_ObjectHideFlags: 0
108 | m_PrefabParentObject: {fileID: 0}
109 | m_PrefabInternal: {fileID: 0}
110 | m_GameObject: {fileID: 837332104}
111 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
112 | m_LocalPosition: {x: 0, y: 0, z: 0}
113 | m_LocalScale: {x: 1, y: 1, z: 1}
114 | m_Children: []
115 | m_Father: {fileID: 0}
116 | --- !u!114 &837332107
117 | MonoBehaviour:
118 | m_ObjectHideFlags: 0
119 | m_PrefabParentObject: {fileID: 0}
120 | m_PrefabInternal: {fileID: 0}
121 | m_GameObject: {fileID: 837332104}
122 | m_Enabled: 1
123 | m_EditorHideFlags: 0
124 | m_Script: {fileID: 11500000, guid: d041b0f345cea4dc4b7405f0d7c99b31, type: 3}
125 | m_Name:
126 | m_EditorClassIdentifier:
127 | --- !u!1 &1115438250
128 | GameObject:
129 | m_ObjectHideFlags: 0
130 | m_PrefabParentObject: {fileID: 0}
131 | m_PrefabInternal: {fileID: 0}
132 | serializedVersion: 4
133 | m_Component:
134 | - 4: {fileID: 1115438255}
135 | - 20: {fileID: 1115438254}
136 | - 92: {fileID: 1115438253}
137 | - 124: {fileID: 1115438252}
138 | - 81: {fileID: 1115438251}
139 | m_Layer: 0
140 | m_Name: Main Camera
141 | m_TagString: MainCamera
142 | m_Icon: {fileID: 0}
143 | m_NavMeshLayer: 0
144 | m_StaticEditorFlags: 0
145 | m_IsActive: 1
146 | --- !u!81 &1115438251
147 | AudioListener:
148 | m_ObjectHideFlags: 0
149 | m_PrefabParentObject: {fileID: 0}
150 | m_PrefabInternal: {fileID: 0}
151 | m_GameObject: {fileID: 1115438250}
152 | m_Enabled: 1
153 | --- !u!124 &1115438252
154 | Behaviour:
155 | m_ObjectHideFlags: 0
156 | m_PrefabParentObject: {fileID: 0}
157 | m_PrefabInternal: {fileID: 0}
158 | m_GameObject: {fileID: 1115438250}
159 | m_Enabled: 1
160 | --- !u!92 &1115438253
161 | Behaviour:
162 | m_ObjectHideFlags: 0
163 | m_PrefabParentObject: {fileID: 0}
164 | m_PrefabInternal: {fileID: 0}
165 | m_GameObject: {fileID: 1115438250}
166 | m_Enabled: 1
167 | --- !u!20 &1115438254
168 | Camera:
169 | m_ObjectHideFlags: 0
170 | m_PrefabParentObject: {fileID: 0}
171 | m_PrefabInternal: {fileID: 0}
172 | m_GameObject: {fileID: 1115438250}
173 | m_Enabled: 1
174 | serializedVersion: 2
175 | m_ClearFlags: 1
176 | m_BackGroundColor: {r: .192156866, g: .301960796, b: .474509805, a: .0196078438}
177 | m_NormalizedViewPortRect:
178 | serializedVersion: 2
179 | x: 0
180 | y: 0
181 | width: 1
182 | height: 1
183 | near clip plane: .300000012
184 | far clip plane: 1000
185 | field of view: 60
186 | orthographic: 0
187 | orthographic size: 5
188 | m_Depth: -1
189 | m_CullingMask:
190 | serializedVersion: 2
191 | m_Bits: 4294967295
192 | m_RenderingPath: -1
193 | m_TargetTexture: {fileID: 0}
194 | m_HDR: 0
195 | m_OcclusionCulling: 1
196 | --- !u!4 &1115438255
197 | Transform:
198 | m_ObjectHideFlags: 0
199 | m_PrefabParentObject: {fileID: 0}
200 | m_PrefabInternal: {fileID: 0}
201 | m_GameObject: {fileID: 1115438250}
202 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
203 | m_LocalPosition: {x: 0, y: 1, z: -10}
204 | m_LocalScale: {x: 1, y: 1, z: 1}
205 | m_Children: []
206 | m_Father: {fileID: 0}
207 |
--------------------------------------------------------------------------------
/GameSaveSystem/Demo/GameSaveSystemDemo.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 495d9b3dd0bc940bd88203dc7d7a1c7b
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveLoadResult.cs:
--------------------------------------------------------------------------------
1 | namespace UnityToolbag
2 | {
3 | ///
4 | /// The actual object returned by the when loading a game save
5 | /// in order to provide additional details about the result.
6 | ///
7 | /// The game save class type.
8 | public class GameSaveLoadResult
9 | where TGameSave : class, IGameSave, new()
10 | {
11 | ///
12 | /// Gets the actual game save object.
13 | ///
14 | public TGameSave save { get; private set; }
15 |
16 | ///
17 | /// Gets a value indicating whether or not the game save was returned from a cache.
18 | ///
19 | public bool wasCached { get; private set; }
20 |
21 | ///
22 | /// Gets a value indicating whether or not a backup file was loaded.
23 | ///
24 | ///
25 | /// While game logic shouldn't do much with this information, it can be nice to notify the
26 | /// player that the game couldn't load their main game save and instead loaded a backup.
27 | ///
28 | public bool usedBackupFile { get; private set; }
29 |
30 | ///
31 | /// Initializes a new instance of the class.
32 | ///
33 | /// The game save object.
34 | /// Whether or not the game save was returned from a cache.
35 | /// Whether or not a backup file was loaded for the save.
36 | public GameSaveLoadResult(TGameSave save, bool wasCached, bool usedBackupFile)
37 | {
38 | this.save = save;
39 | this.wasCached = wasCached;
40 | this.usedBackupFile = usedBackupFile;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveLoadResult.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 70b7f1859acf0432389d3114b27f5d63
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveSystem.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 |
6 | namespace UnityToolbag
7 | {
8 | ///
9 | /// A game save system that handles file management and save file caching.
10 | ///
11 | public static class GameSaveSystem
12 | {
13 | private static GameSaveSystemSettings _settings;
14 | private static string _fileSaveLocation;
15 |
16 | ///
17 | /// Gets a value indicating whether or not the GameSaveSystem has been initialized.
18 | ///
19 | public static bool isInitialized { get; private set; }
20 |
21 | ///
22 | /// Gets the folder where the game saves are stored;
23 | ///
24 | public static string saveLocation
25 | {
26 | get
27 | {
28 | ThrowIfNotInitialized();
29 | return _fileSaveLocation;
30 | }
31 | }
32 |
33 | ///
34 | /// Initializes the system with the provided settings.
35 | ///
36 | /// The settings to configure the system with.
37 | public static void Initialize(GameSaveSystemSettings settings)
38 | {
39 | if (isInitialized) {
40 | throw new InvalidOperationException("GameSaveSystem is already initialized. Cannot initialize again!");
41 | }
42 |
43 | // Validate our input
44 | if (settings == null) {
45 | throw new ArgumentNullException("settings");
46 | }
47 | if (string.IsNullOrEmpty(settings.gameName)) {
48 | throw new ArgumentException("The gameName in the settings must be a non-empty string");
49 | }
50 | if (settings.gameName.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) != -1) {
51 | throw new ArgumentException("The gameName in the settings contains illegal characters");
52 | }
53 | if (settings.useRollingBackups && settings.backupCount <= 0) {
54 | throw new ArgumentException("useRollingBackups is true but backupCount isn't a positive value");
55 | }
56 |
57 | // Copy the settings locally so we can retain a new instance (so it can't be changed out from under us)
58 | _settings = settings.Clone();
59 |
60 | // Find the base path for where we want to save game saves. Unity's persistentDataPath is generally unacceptable
61 | // to me. In Windows it uses some AppData folder, in OS X it (quite incorrectly) puts saves into a Cache folder,
62 | // and I have no idea what they use on Linux but I'm assuming it's not what I want.
63 | #if UNITY_STANDALONE_WIN
64 | _fileSaveLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "My Games");
65 | #elif UNITY_STANDALONE_OSX
66 | _fileSaveLocation = Path.Combine(Environment.GetEnvironmentVariable("HOME"), "Library/Application Support");
67 | #elif UNITY_STANDALONE_LINUX
68 | string home = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
69 | if (string.IsNullOrEmpty(home)) {
70 | home = Environment.GetEnvironmentVariable("HOME");
71 | }
72 | _fileSaveLocation = Path.Combine(home, ".local/share");
73 | #else
74 | // For any non Mac/Windows/Linux platform, we'll default back to the persistentDataPath since we know it should work.
75 | _fileSaveLocation = Application.persistentDataPath;
76 | #endif
77 |
78 | // Do final cleanup on the path if we're on a desktop platform
79 | #if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX
80 | // Company name is optional so we check before appending it
81 | if (!string.IsNullOrEmpty(_settings.companyName)) {
82 | _fileSaveLocation = Path.Combine(_fileSaveLocation, _settings.companyName);
83 | }
84 |
85 | _fileSaveLocation = Path.Combine(_fileSaveLocation, _settings.gameName);
86 | _fileSaveLocation = Path.GetFullPath(_fileSaveLocation);
87 | #endif
88 |
89 | // Ensure the directory for saves exists.
90 | Directory.CreateDirectory(_fileSaveLocation);
91 |
92 | isInitialized = true;
93 | }
94 |
95 | ///
96 | /// Loads a game save.
97 | ///
98 | /// The game save class type.
99 | /// The name of the gamesave to load.
100 | ///
101 | /// If true the save will always be read from disk. If false the system will return a cached instance if available.
102 | ///
103 | /// An that can be used to track and examine the load results.
104 | public static IFuture> Load(string name, bool forceFromDisk = false)
105 | where TGameSave : class, IGameSave, new()
106 | {
107 | ThrowIfNotInitialized();
108 |
109 | Future> future = new Future>();
110 |
111 | TGameSave save = null;
112 |
113 | if (GameSaveCache.TryGetSave(name, out save) && !forceFromDisk) {
114 | future.Assign(new GameSaveLoadResult(save, true, false));
115 | }
116 | else {
117 | if (save == null) {
118 | save = new TGameSave();
119 | GameSaveCache.Set(name, save);
120 | }
121 | else {
122 | save.Reset();
123 | }
124 |
125 | future.Process(() =>
126 | {
127 | bool usedBackup = false;
128 |
129 | if (_settings.useRollingBackups) {
130 | usedBackup = DoLoadWithBackups(save, name);
131 | }
132 | else {
133 | using (var stream = File.OpenRead(GetGameSavePath(name))) {
134 | save.Load(stream);
135 | }
136 | }
137 |
138 | return new GameSaveLoadResult(save, false, usedBackup);
139 | });
140 | }
141 |
142 | return future;
143 | }
144 |
145 | ///
146 | /// Saves a game save.
147 | ///
148 | /// The game save class type.
149 | /// The name of the gamesave to load.
150 | /// The game save to save.
151 | /// An that can be used to track completion of the save operation.
152 | public static IFuture Save(string name, TGameSave save)
153 | where TGameSave : class, IGameSave, new()
154 | {
155 | ThrowIfNotInitialized();
156 |
157 | GameSaveCache.Set(name, save);
158 |
159 | return new Future().Process(() =>
160 | {
161 | if (_settings.useRollingBackups) {
162 | DoSaveWithBackups(save, name);
163 | }
164 | else {
165 | using (var stream = File.Create(GetGameSavePath(name))) {
166 | save.Save(stream);
167 | }
168 | }
169 | return true;
170 | });
171 | }
172 |
173 | private static bool DoLoadWithBackups(IGameSave save, string name)
174 | {
175 | string mainSavePath = GetGameSavePath(name);
176 |
177 | // Try loading the regular save. Most times this should work fine.
178 | try {
179 | using (var stream = File.OpenRead(mainSavePath)) {
180 | save.Load(stream);
181 | }
182 |
183 | // If Load didn't throw, we're good to go and can return that we
184 | // didn't need to load a backup save.
185 | return false;
186 | }
187 | catch {
188 | save.Reset();
189 | }
190 |
191 | // We go through and try loading all of the available backups
192 | var foundGoodSave = false;
193 | var backupIndex = 0;
194 | for (backupIndex = 0; !foundGoodSave && backupIndex < _settings.backupCount; backupIndex++) {
195 | // Try loading the file.
196 | try {
197 | var path = GetBackupSavePath(name, backupIndex);
198 | using (var stream = File.OpenRead(path)) {
199 | save.Load(stream);
200 | }
201 |
202 | foundGoodSave = true;
203 |
204 | // break so we don't increment the backupIndex again
205 | break;
206 | }
207 | catch {
208 | save.Reset();
209 | }
210 | }
211 |
212 | // At this point we either know that A) we loaded a backup successfully or B) all the saves are bad.
213 | // So we need to clean up our saves to get rid of the bad ones.
214 |
215 | // We know the main save failed so we can delete that
216 | if (File.Exists(mainSavePath)) {
217 | File.Delete(mainSavePath);
218 | }
219 |
220 | // Delete all backups newer than the one we were able to load. This might delete all of them if no saves were good.
221 | for (int i = backupIndex - 1; i >= 0; i--) {
222 | var path = GetBackupSavePath(name, i);
223 | if (File.Exists(path)) {
224 | File.Delete(path);
225 | }
226 | }
227 |
228 | if (foundGoodSave) {
229 | // If we did find a save, make that save our main save
230 | Debug.Log("Moving backup " + GetBackupSavePath(name, backupIndex) + " to " + mainSavePath);
231 | MoveFile(GetBackupSavePath(name, backupIndex), mainSavePath);
232 |
233 | // Move up the remaining backups
234 | for (int i = backupIndex; i <= _settings.backupCount; i++) {
235 | var path1 = GetBackupSavePath(name, i);
236 | if (File.Exists(path1)) {
237 | File.Delete(path1);
238 | }
239 |
240 | if (i < _settings.backupCount) {
241 | var path2 = GetBackupSavePath(name, i + 1);
242 | if (File.Exists(path2)) {
243 | MoveFile(path2, path1);
244 | }
245 | }
246 | }
247 | }
248 |
249 | // If we didn't find any good saves, throw an exception so the future will receive the error and
250 | // games can choose how to handle it.
251 | if (!foundGoodSave) {
252 | throw new FileNotFoundException("No game save found with name '" + name + "'");
253 | }
254 |
255 | // Return true because if we got here we know we used a backup file
256 | return true;
257 | }
258 |
259 | private static void MoveFile(string src, string dst)
260 | {
261 | #if UNITY_WEBPLAYER
262 | File.Copy(src, dst);
263 | File.Delete(src);
264 | #else
265 | File.Move(src, dst);
266 | #endif
267 | }
268 |
269 | private static void DoSaveWithBackups(IGameSave save, string name)
270 | {
271 | var mainSavePath = GetGameSavePath(name);
272 | var tempPath = mainSavePath + ".temp";
273 |
274 | // Start by attempting the save to a temp file
275 | try {
276 | using (var stream = File.Create(tempPath)) {
277 | save.Save(stream);
278 | }
279 | }
280 | catch {
281 | // Remove the temp file before leaving scope if saving threw an exception
282 | try {
283 | if (File.Exists(tempPath)) {
284 | File.Delete(tempPath);
285 | }
286 | }
287 | catch { }
288 |
289 | // Let the exception continue up
290 | throw;
291 | }
292 |
293 | // Saving succeeded so we need to move from the temp path to the main path.
294 | // First up we shift down all the backup files
295 | for (int i = _settings.backupCount - 2; i >= 0; i--) {
296 | var path = GetBackupSavePath(name, i);
297 | if (File.Exists(path)) {
298 | var nextPath = GetBackupSavePath(name, i + 1);
299 | if (File.Exists(nextPath)) {
300 | File.Delete(nextPath);
301 | }
302 | MoveFile(path, nextPath);
303 | }
304 | }
305 |
306 | // Then move the current main save into the first backup slot
307 | if (File.Exists(mainSavePath)) {
308 | MoveFile(mainSavePath, GetBackupSavePath(name, 0));
309 | }
310 |
311 | // Then we can just move the new file into place
312 | MoveFile(tempPath, mainSavePath);
313 | }
314 |
315 | private static void ThrowIfNotInitialized()
316 | {
317 | if (!isInitialized) {
318 | throw new InvalidOperationException("You must call Initialize first!");
319 | }
320 | }
321 |
322 | private static string GetGameSavePath(string name)
323 | {
324 | return Path.Combine(_fileSaveLocation, name + ".save");
325 | }
326 |
327 | private static string GetBackupSavePath(string name, int index)
328 | {
329 | return GetGameSavePath(name) + ".backup" + (index + 1);
330 | }
331 |
332 | // Static generic class for caching individual types of game saves.
333 | private static class GameSaveCache
334 | where TGameSave : class, IGameSave, new()
335 | {
336 | private static readonly Dictionary _cache = new Dictionary();
337 |
338 | public static bool TryGetSave(string name, out TGameSave save)
339 | {
340 | return _cache.TryGetValue(name, out save);
341 | }
342 |
343 | public static void Set(string name, TGameSave save)
344 | {
345 | _cache[name] = save;
346 | }
347 | }
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveSystem.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6deba010d53c74630968e6bbbc80ad72
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveSystemSettings.cs:
--------------------------------------------------------------------------------
1 | namespace UnityToolbag
2 | {
3 | ///
4 | /// A settings object used to initialize the GameSaveSystem.
5 | ///
6 | public class GameSaveSystemSettings
7 | {
8 | ///
9 | /// The name of the company used for constructing the save location. Can be null.
10 | ///
11 | public string companyName;
12 |
13 | ///
14 | /// The name of the game for constructing the save lcoation.
15 | ///
16 | public string gameName;
17 |
18 | ///
19 | /// Whether or not to use rolling backups for game saves.
20 | ///
21 | public bool useRollingBackups = true;
22 |
23 | ///
24 | /// Number of backups to store if useRollingBackups is true.
25 | ///
26 | public int backupCount = 2;
27 |
28 | ///
29 | /// Creates a copy of the settings object.
30 | ///
31 | public GameSaveSystemSettings Clone()
32 | {
33 | return new GameSaveSystemSettings
34 | {
35 | companyName = this.companyName,
36 | gameName = this.gameName,
37 | useRollingBackups = this.useRollingBackups,
38 | backupCount = this.backupCount,
39 | };
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/GameSaveSystem/GameSaveSystemSettings.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ed74685f9fd27413fb7a39d971f7c3f3
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/IGameSave.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace UnityToolbag
4 | {
5 | ///
6 | /// Defines the interface of a game save object that can be used with .
7 | ///
8 | public interface IGameSave
9 | {
10 | ///
11 | /// Resets the game save to a blank state.
12 | ///
13 | void Reset();
14 |
15 | ///
16 | /// Saves the game state into the given stream.
17 | ///
18 | /// The stream into which the game save should be saved.
19 | void Save(Stream stream);
20 |
21 | ///
22 | /// Loads the game state from the given stream.
23 | ///
24 | /// The stream from which the game save should be loaded.
25 | void Load(Stream stream);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/GameSaveSystem/IGameSave.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e0f958665cd304287907579a7f61a913
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/GameSaveSystem/README.md:
--------------------------------------------------------------------------------
1 | Game Save System
2 | ===
3 |
4 | This is a game save management system I wrote for our games. This system implements a number of features that make dealing with game saves easier:
5 |
6 | 1. All saving and loading occurs off the main thread using futures. This ensures games are responsive and don't lock up during file operations.
7 | 2. File saves are cached so trying to load a save that is in the cache doesn't hit the disk (unless you pass in an argument to force it to).
8 | 3. The system can automatically handle rolling backups of game saves for cases where loading fails. This provides a nice fallback system in cases where game saves become corrupt. Both disabling the backups and setting the number of backup files is configurable with the `GameSaveSystemSettings` passed to `GameSaveSystem.Initialize`.
9 | 4. Unity's `Application.persistentDataPath` is almost always not where game saves should go. This game save system uses much nicer paths for game saves.
10 |
11 | This system relies on both the `IFuture` interface and `Future` class from the [Future](https://github.com/nickgravelyn/UnityToolbag/tree/master/Future) library, so make sure you include them in your project if you want to use this sytem.
12 |
13 | Usage
14 | ---
15 |
16 | 1. Create one or more classes that implement `IGameSave`.
17 | 2. At the start of your game, call `GameSaveSystem.Initialize(GameSaveSystemSetting settings)` to configure the system to your liking.
18 | 3. Use `GameSaveSystem.Load` to load saves and `GameSaveSystem.Save` to save them.
19 |
20 | All of the code files are heavily commented and should be referenced for more documentation. Additionally there is a Demo folder that has an extremely minimal sample showing the usage of the system.
21 |
22 | Save Locations
23 | ---
24 |
25 | The game save system uses custom paths on desktop platforms to form more correct paths than Unity, whose `Application.persistentDataPath` is at best not ideal and at worst a terrible path for game saves.
26 |
27 | If you'd prefer to not include your company name in the file paths below, simply keep the `companyName` field of the `GameSaveSystemSettings` object you pass to `GameSaveSystem.Initialize` as null. If the field is null, the `{Company}` parts of the paths below will be omitted.
28 |
29 | ### Windows
30 |
31 | On Windows, the persistent data path will stick your game saves in `C:\Users\{User}\AppData\LocalLow\{Company}\{Game}`. Of all the paths this is the least bothersome, but the AppData directory itself is a hidden folder which can make it hard for gamers to back up game saves manually (most automatic backup software will pick this directory up). Instead this game system uses `C:\Users\{User}\Documents\My Games\{Company}\{Game}` which is pretty much what RockPaperShotgun [recommended](http://www.rockpapershotgun.com/2012/01/24/start-it-the-place-to-put-save-games/) for game saves.
32 |
33 | ### Mac OS X
34 |
35 | On OS X Unity picks a terrible folder for the persistent data path, using `~/Library/Caches/{Company}/{Game}`. From Apple's [documentation](https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html):
36 |
37 | > Contains cached data that can be regenerated as needed. Apps should never rely on the existence of cache files.
38 |
39 | Game saves definitely don't fit that definition.
40 |
41 | Some games use `~/Library/Preferences` but that also isn't correct by Apple's guidelines:
42 |
43 | > Contains the user’s preferences. You should never create files in this directory yourself. To get or set preference values, you should always use the NSUserDefaults class or an equivalent system-provided interface.
44 |
45 | As such this system puts game saves where they should go, at `~/Library/Application Support/{Company}/{Game}`.
46 |
47 | _Technically the Apple documentation recommends using the bundle identifier for your directory such as `~/Library/Application Support/com.example.MyApp/` but in reality nobody does that, not even Apple who put data files from the Mac App Store at `~/Library/Application Support/App Store`._
48 |
49 | ### Linux
50 |
51 | _Caveat: I'm not a Linux user so I may not be quite right here, but from those I've spoken to, the path I'm using in this system is an acceptable location for game saves._
52 |
53 | Linux is tricky since there doesn't appear to be one great standard. However Unity chose to put all of the game saves created in `~/.config/unity3d/{Company}/{Game}` which I think is a poor decision because now your users must know that your game uses Unity in order to find the game saves. To be more appropriate this system uses `($XDG_DATA_HOME|$HOME)/.local/share/{Company}/{Game}`. To find the root the system first resolves the environment variable `XDG_DATA_HOME`, falling back to regular `HOME` if that's not available.
54 |
--------------------------------------------------------------------------------
/GameSaveSystem/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1bca2fc8ca96446f49d6cf7c7e970560
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/ImmediateWindow.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e6c3a2e8d18ad43f583fd8294e615790
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ImmediateWindow/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4227337d6ce32f5429536225ebe7fdb6
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ImmediateWindow/Editor/CodeCompiler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.CodeDom.Compiler;
3 | using System.Reflection;
4 | using Microsoft.CSharp;
5 | using UnityEditor;
6 | using UnityEngine;
7 |
8 | namespace UnityToolbag
9 | {
10 | ///
11 | /// Provides a simple interface for dynamically compiling C# code.
12 | ///
13 | public static class CodeCompiler
14 | {
15 | ///
16 | /// Compiles a method body of C# script, wrapped in a basic void-returning method.
17 | ///
18 | /// The text of the script to place inside a method.
19 | /// The compiler errors and warnings from compilation.
20 | /// The compiled method if compilation succeeded.
21 | /// True if compilation was a success, false otherwise.
22 | public static bool CompileCSharpImmediateSnippet(string methodText, out CompilerErrorCollection errors, out MethodInfo methodIfSucceeded)
23 | {
24 | // Wrapper text so we can compile a full type when given just the body of a method
25 | string methodScriptWrapper = @"
26 | using UnityEngine;
27 | using UnityEditor;
28 | using System.Collections;
29 | using System.Collections.Generic;
30 | using System.Text;
31 | using System.Xml;
32 | using System.Linq;
33 | public static class CodeSnippetWrapper
34 | {{
35 | public static void PerformAction()
36 | {{
37 | {0};
38 | }}
39 | }}";
40 |
41 | // Default method to null
42 | methodIfSucceeded = null;
43 |
44 | // Compile the full script
45 | Assembly assembly;
46 | if (CompileCSharpScript(string.Format(methodScriptWrapper, methodText), out errors, out assembly)) {
47 | // If compilation succeeded, we can use reflection to get the method and pass that back to the user
48 | methodIfSucceeded = assembly.GetType("CodeSnippetWrapper").GetMethod("PerformAction", BindingFlags.Static | BindingFlags.Public);
49 | return true;
50 | }
51 |
52 | // Compilation failed, caller has the errors, return false
53 | return false;
54 | }
55 |
56 | ///
57 | /// Compiles a C# script as if it were a file in your project.
58 | ///
59 | /// The text of the script.
60 | /// The compiler errors and warnings from compilation.
61 | /// The compiled assembly if compilation succeeded.
62 | /// True if compilation was a success, false otherwise.
63 | public static bool CompileCSharpScript(string scriptText, out CompilerErrorCollection errors, out Assembly assemblyIfSucceeded)
64 | {
65 | var codeProvider = new CSharpCodeProvider();
66 | var compilerOptions = new CompilerParameters();
67 |
68 | // We want a DLL and we want it in memory
69 | compilerOptions.GenerateExecutable = false;
70 | compilerOptions.GenerateInMemory = true;
71 |
72 | // Add references for UnityEngine and UnityEditor DLLs
73 | compilerOptions.ReferencedAssemblies.Add(typeof(Vector2).Assembly.Location);
74 | compilerOptions.ReferencedAssemblies.Add(typeof(EditorApplication).Assembly.Location);
75 |
76 | // Default to null output parameters
77 | errors = null;
78 | assemblyIfSucceeded = null;
79 |
80 | // Compile the assembly from the source script text
81 | CompilerResults result = codeProvider.CompileAssemblyFromSource(compilerOptions, scriptText);
82 |
83 | // Store the errors for the caller. even on successful compilation, we may have warnings.
84 | errors = result.Errors;
85 |
86 | // See if any errors are actually errors. if so return false
87 | foreach (CompilerError e in errors) {
88 | if (!e.IsWarning) {
89 | return false;
90 | }
91 | }
92 |
93 | // Otherwise we pass back the compiled assembly and return true
94 | assemblyIfSucceeded = result.CompiledAssembly;
95 | return true;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ImmediateWindow/Editor/CodeCompiler.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4c86143930ce3402a98b6bd1c9a4f314
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ImmediateWindow/Editor/ImmediateWindow.cs:
--------------------------------------------------------------------------------
1 | using System.CodeDom.Compiler;
2 | using System.Reflection;
3 | using System.Text;
4 | using UnityEditor;
5 | using UnityEngine;
6 |
7 | namespace UnityToolbag
8 | {
9 | ///
10 | /// Provides an editor window for quickly compiling and running snippets of code.
11 | ///
12 | public class ImmediateWindow : EditorWindow
13 | {
14 | private const string EditorPrefsKey = "UnityToolbag.ImmediateWindow.LastText";
15 |
16 | // Positions for the two scroll views
17 | private Vector2 _scrollPos;
18 | private Vector2 _errorScrollPos;
19 |
20 | // The script text string
21 | private string _scriptText = string.Empty;
22 |
23 | // Stored away compiler errors (if any) and the compiled method
24 | private CompilerErrorCollection _compilerErrors = null;
25 | private MethodInfo _compiledMethod = null;
26 |
27 | void OnEnable()
28 | {
29 | if (EditorPrefs.HasKey(EditorPrefsKey)) {
30 | _scriptText = EditorPrefs.GetString(EditorPrefsKey);
31 | }
32 | }
33 |
34 | void OnGUI()
35 | {
36 | // Make a scroll view for the text area
37 | _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
38 |
39 | // Place a text area in the scroll view
40 | string newScriptText = EditorGUILayout.TextArea(_scriptText, GUILayout.ExpandHeight(true));
41 |
42 | // If the script updated save the script and remove the compiled method as it's no longer valid
43 | if (_scriptText != newScriptText) {
44 | _scriptText = newScriptText;
45 | EditorPrefs.SetString(EditorPrefsKey, _scriptText);
46 | _compiledMethod = null;
47 | }
48 |
49 | EditorGUILayout.EndScrollView();
50 |
51 | // Setup the compile/run button
52 | if (GUILayout.Button(_compiledMethod == null ? "Compile + Run" : "Run")) {
53 | // If the method is already compiled or if we successfully compile the script text, invoke the method
54 | if (_compiledMethod != null || CodeCompiler.CompileCSharpImmediateSnippet(_scriptText, out _compilerErrors, out _compiledMethod)) {
55 | _compiledMethod.Invoke(null, null);
56 | }
57 | }
58 |
59 | // If we have any errors, we display them in their own scroll view
60 | if (_compilerErrors != null && _compilerErrors.Count > 0) {
61 | // Build up one string for errors and one for warnings
62 | StringBuilder errorString = new StringBuilder();
63 | StringBuilder warningString = new StringBuilder();
64 |
65 | foreach (CompilerError e in _compilerErrors) {
66 | if (e.IsWarning) {
67 | warningString.AppendFormat("Warning on line {0}: {1}\n", e.Line, e.ErrorText);
68 | }
69 | else {
70 | errorString.AppendFormat("Error on line {0}: {1}\n", e.Line, e.ErrorText);
71 | }
72 | }
73 |
74 | // Remove trailing new lines from both strings
75 | if (errorString.Length > 0) {
76 | errorString.Length -= 2;
77 | }
78 |
79 | if (warningString.Length > 0) {
80 | warningString.Length -= 2;
81 | }
82 |
83 | // Make a simple UI layout with a scroll view and some labels
84 | GUILayout.Label("Errors and warnings:");
85 | _errorScrollPos = EditorGUILayout.BeginScrollView(_errorScrollPos, GUILayout.MaxHeight(100));
86 |
87 | if (errorString.Length > 0) {
88 | GUILayout.Label(errorString.ToString());
89 | }
90 |
91 | if (warningString.Length > 0) {
92 | GUILayout.Label(warningString.ToString());
93 | }
94 |
95 | EditorGUILayout.EndScrollView();
96 | }
97 | }
98 |
99 | ///
100 | /// Fired when the user chooses the menu item
101 | ///
102 | [MenuItem("Window/Immediate %#I")]
103 | static void Init()
104 | {
105 | // Get the window, show it, and give it focus
106 | var window = EditorWindow.GetWindow("Immediate");
107 | window.Show();
108 | window.Focus();
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/ImmediateWindow/Editor/ImmediateWindow.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5dbbcc8d472bd4d438f4dbe711573e39
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ImmediateWindow/README.md:
--------------------------------------------------------------------------------
1 | Immediate Window
2 | ===
3 |
4 | Leverage the power of C# right from the editor for quickly lining up objects, randomly adjusting scales, swapping materials, creating objects from code, or anything else exposed through the Unity Engine or Editor APIs.
5 |
6 |
7 | Quick Start
8 | ---
9 |
10 | 1. Open the Immediate Window choosing Immediate from the Window menu.
11 | 2. Type code you want to execute in the text area. The code is inserted into a dummy class and method so you can type any code that is valid C# for the body of a method.
12 | 3. Click "Compile + Run" to compile the code and run it.
13 | 4. After code has been compiled once, you can continue running it over and over without recompiling. Once you change the text, you must recompile.
14 |
15 | Any compilation errors are printed below the Compile + Run button for quick access to seeing what you need to fix.
16 |
17 |
18 | Example Scripts
19 | ---
20 |
21 | Here are a few sample scripts that are fun to play with.
22 |
23 | Creating a new object from code:
24 |
25 | var go = new GameObject("Immediate Object");
26 | go.AddComponent();
27 |
28 | Changing the scale of the test cubes:
29 |
30 | foreach (var t in Selection.transforms)
31 | {
32 | Vector3 scale = t.localScale;
33 | scale.y = Random.value * 3f + 1f;
34 | t.localScale = scale;
35 |
36 | Vector3 position = t.position;
37 | position.y = scale.y / 2;
38 | t.position = position;
39 | }
40 |
41 | Creating a checkerboard layout of boxes
42 |
43 | for (int x = -5; x <= 5; x++)
44 | {
45 | for (int z = -5; z <= 5; z++)
46 | {
47 | if ((x % 2 == 0 && z % 2 != 0) || (x % 2 != 0 && z % 2 == 0))
48 | {
49 | var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
50 | cube.name = "Checkerboard Cube";
51 | cube.transform.position = new Vector3(x, 0, z);
52 | }
53 | }
54 | }
55 |
56 | Easily deleting all the checkerboard cubes the above script made
57 |
58 | GameObject cube = null;
59 | while ((cube = GameObject.Find("Checkerboard Cube")) != null)
60 | {
61 | cube.active = false;
62 | Object.DestroyImmediate(cube);
63 | }
64 |
--------------------------------------------------------------------------------
/ImmediateWindow/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: da3abd54f2356421aafa0e093cee9224
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015, Nick Gravelyn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 61873d6e633074d3ea9dd2653e78ce4f
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | UnityToolbag
2 | ===
3 |
4 | This repo is a host for any little Unity scripts I write that are simple and easy for others to leverage. Each folder has its own README to explain the usage in more depth than here. All scripts are being written with Unity 5.0 and may or may not work in earlier versions.
5 |
6 |
7 | Features
8 | ---
9 |
10 | - [CacheBehaviour](CacheBehaviour) - A drop-in replacement for `MonoBehaviour` as a script base class that provides caching of all standard properties.
11 | - [Dispatcher](Dispatcher) - Provides a mechanism for invoking code on the main thread from background threads.
12 | - [DrawTitleSafeArea](DrawTitleSafeArea) - Simple component you add to a camera to render the title safe area.
13 | - [EditorTools](EditorTools) - Misc tools for making it easier to build editor UI.
14 | - [ExclusiveChildren](ExclusiveChildren) - Helper script for managing objects in a hierarchy that represent mutually exclusive options (like a set of menu screens)
15 | - [Future](Future) - Simple implementation of the [future](http://en.wikipedia.org/wiki/Futures_and_promises) concept.
16 | - [GameSaveSystem](GameSaveSystem) - A helper system for game saves to provide automatic backups and background thread processing along with better game save file paths.
17 | - [ImmediateWindow](ImmediateWindow) - An editor window that allows executing manual C# snippets.
18 | - [ScriptableObjectUtility](ScriptableObjectUtility) - An editor class to help with creating `ScriptableObject` subclasses.
19 | - [SimpleSpriteAnimation](SimpleSpriteAnimation) - A very basic system for a simpler frame based animation for Unity's 2D system.
20 | - [SnapToSurface](SnapToSurface) - Editor tools to assist in positioning objects.
21 | - [SortingLayer](SortingLayer) - Tools for working with Unity's new sorting layers.
22 | - [TimeScaleIndependentUpdate](TimeScaleIndependentUpdate) - Components to make it easier to continue animations when `Time.timeScale` is set to 0 (i.e. paused).
23 | - [UnityConstants](UnityConstants) - Tool for generating a C# script containing the names and values for tags, layers, sorting layers, and scenes.
24 | - [UnityLock](UnityLock) - Basic tool for locking objects in the scene to minimize accidental edits while working.
25 |
26 |
27 | Usage
28 | ---
29 |
30 | Simply clone the repository into the 'Assets' folder of a Unity project and you're good to go. If you're already using Git, you can use a submodule to check out into Assets without the Toolbag getting added to your repository.
31 |
32 | Alternatively you can just cherry pick the features you want and copy only those folders into your project. Be careful, though, as some of the features may depend on others. See the individual feature README files to find out.
33 |
34 | Any component types are exposed through the component menu under UnityToolbag:
35 |
36 | 
37 |
38 |
39 | Shameless Plug
40 | ---
41 |
42 | If you find any code in here to be useful and feel so inclined, you can help me out by picking up a copy of my company's first game [Shipwreck](http://brushfiregames.com/shipwreck). Absolutely not required (this code is free) but definitely appreciated. :)
43 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ab759bb8e7bea4b2d8aebd9645ea2425
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4d449a7baf512476a9fc9cff6087a220
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b3fe2ef8543dc4d99a35fe83b2bdad23
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility/Editor/ScriptableObjectUtility.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System.IO;
4 |
5 | namespace UnityToolbag
6 | {
7 | public static class ScriptableObjectUtility
8 | {
9 | public static T CreateAsset() where T : ScriptableObject
10 | {
11 | T asset = ScriptableObject.CreateInstance();
12 |
13 | string path = AssetDatabase.GetAssetPath(Selection.activeObject);
14 | if (string.IsNullOrEmpty(path))
15 | {
16 | path = "Assets";
17 | }
18 | else if (!string.IsNullOrEmpty(Path.GetExtension(path)))
19 | {
20 | path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
21 | }
22 |
23 | string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(string.Format("{0}/New {1}.asset", path, typeof(T)));
24 |
25 | AssetDatabase.CreateAsset(asset, assetPathAndName);
26 |
27 | AssetDatabase.SaveAssets();
28 | EditorUtility.FocusProjectWindow();
29 | Selection.activeObject = asset;
30 |
31 | return asset;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility/Editor/ScriptableObjectUtility.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5d83c264a4a944946a6d74f94dc07c03
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility/README.md:
--------------------------------------------------------------------------------
1 | Scriptable Object Utility
2 | ===
3 |
4 | This is just a little helper I grabbed from the Unify wiki: [http://wiki.unity3d.com/index.php?title=CreateScriptableObjectAsset](http://wiki.unity3d.com/index.php?title=CreateScriptableObjectAsset). It provides a quick way to create ScriptableObject subclasses for custom assets.
5 |
--------------------------------------------------------------------------------
/ScriptableObjectUtility/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 27fa3185e025e4bcb8845cfa50d17789
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: eae98c830f7f143ffb3bd7d47ead205f
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ea429fa9a918c48a38f2d07b189ed9a3
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/Editor/SpriteFrameAnimationEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 |
4 | namespace UnityToolbag
5 | {
6 | [CustomEditor(typeof(SpriteFrameAnimation))]
7 | public class SpriteFrameAnimationEditor : Editor
8 | {
9 | [UnityEditor.MenuItem("Assets/Create/UnityToolbag/Sprite Frame Animation")]
10 | static void Create()
11 | {
12 | ScriptableObjectUtility.CreateAsset();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/Editor/SpriteFrameAnimationEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 72a8c3f01f5304d97acd0ec038e2b283
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/README.md:
--------------------------------------------------------------------------------
1 | Simple Sprite Animation
2 | ===
3 |
4 | A simpler frame based animation system for the new sprite feature of Unity 4.3. The animation is an asset you create and configure with frames, a frame duration, and a loop option. Then you put an animator on some object, assign its SpriteRenderer, and give it one or more animations. You can optionally have an animation play by default.
5 |
6 | This isn't supposed to be super powerful to compete with the options of the Unity animation system, but it does provide a handy way to configure basic frame based sprite animations.
7 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 364c42beaa1754719b4e84fe244b15d1
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/SpriteFrameAnimation.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 |
4 | namespace UnityToolbag
5 | {
6 | public class SpriteFrameAnimation : ScriptableObject
7 | {
8 | public bool loop;
9 | public float frameDuration = 15.0f; // duration is in milliseconds
10 | public Sprite[] frames = new Sprite[0];
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/SpriteFrameAnimation.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0010fb3aa17604badbbdf5d9dd92f185
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/SpriteFrameAnimator.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 |
4 | namespace UnityToolbag
5 | {
6 | public enum SpriteFrameAnimationResetOption
7 | {
8 | DontReset,
9 | ResetIfNew,
10 | ResetAlways
11 | }
12 |
13 | [AddComponentMenu("UnityToolbag/Sprite Frame Animator")]
14 | public class SpriteFrameAnimator : MonoBehaviour
15 | {
16 | private SpriteFrameAnimation _currentAnimation;
17 |
18 | private int _frame;
19 | private float _timer;
20 |
21 | [SerializeField]
22 | private SpriteRenderer _spriteRenderer;
23 |
24 | [SerializeField]
25 | private SpriteFrameAnimation[] _animations;
26 |
27 | [SerializeField]
28 | private string _startAnimation = string.Empty;
29 |
30 | [SerializeField]
31 | private bool _playOnStart = false;
32 |
33 | public bool isAnimationComplete
34 | {
35 | get
36 | {
37 | if (_currentAnimation == null) {
38 | return true;
39 | }
40 |
41 | if (_currentAnimation.loop) {
42 | return false;
43 | }
44 |
45 | return _frame == _currentAnimation.frames.Length - 1 &&
46 | _timer == _currentAnimation.frameDuration;
47 | }
48 | }
49 |
50 | public void Play(string name)
51 | {
52 | Play(name, SpriteFrameAnimationResetOption.ResetIfNew);
53 | }
54 |
55 | public void Play(string name, SpriteFrameAnimationResetOption resetOption)
56 | {
57 | SpriteFrameAnimation newAnimation = null;
58 |
59 | foreach (var anim in _animations){
60 | if (anim.name == name) {
61 | newAnimation = anim;
62 | break;
63 | }
64 | }
65 |
66 | if (newAnimation == null) {
67 | _currentAnimation = null;
68 | return;
69 | }
70 |
71 | switch (resetOption) {
72 | case SpriteFrameAnimationResetOption.ResetIfNew: {
73 | if (newAnimation != _currentAnimation) {
74 | _frame = 0;
75 | _timer = 0;
76 | }
77 | break;
78 | }
79 | case SpriteFrameAnimationResetOption.ResetAlways: {
80 | _frame = 0;
81 | _timer = 0;
82 | break;
83 | }
84 | default: {
85 | break;
86 | }
87 | }
88 |
89 | _currentAnimation = newAnimation;
90 | if (_currentAnimation != null) {
91 | UpdateWithFrameData();
92 | }
93 | }
94 |
95 | public void Stop()
96 | {
97 | _currentAnimation = null;
98 | }
99 |
100 | public void Stop(int stopOnIndex)
101 | {
102 | if (_currentAnimation != null) {
103 | _frame = stopOnIndex;
104 | UpdateWithFrameData();
105 | _currentAnimation = null;
106 | }
107 | }
108 |
109 | void Awake()
110 | {
111 | _spriteRenderer = GetComponent();
112 | }
113 |
114 | void Start()
115 | {
116 | if (_playOnStart) {
117 | Play(_startAnimation);
118 | }
119 | }
120 |
121 | void Update()
122 | {
123 | if (_currentAnimation != null) {
124 | if (!isAnimationComplete) {
125 | _timer += Time.deltaTime * 1000f;
126 | }
127 |
128 | if (_timer >= _currentAnimation.frameDuration) {
129 | _timer -= _currentAnimation.frameDuration;
130 |
131 | if (!isAnimationComplete || _currentAnimation.loop) {
132 | if (_currentAnimation.loop) {
133 | _frame = (_frame + 1) % _currentAnimation.frames.Length;
134 | }
135 | else if (_frame < _currentAnimation.frames.Length) {
136 | _frame++;
137 | }
138 |
139 | UpdateWithFrameData();
140 | }
141 | }
142 | }
143 | }
144 |
145 | void UpdateWithFrameData()
146 | {
147 | _spriteRenderer.sprite = _currentAnimation.frames[_frame];
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/SimpleSpriteAnimation/SpriteFrameAnimator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dd570e82a7e114d58b2e6e670bb8b2b4
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SnapToSurface.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e10e4ca17f75e4ee5b3221b7b0e725c4
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SnapToSurface/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 18a39d4140a6c4d01b9b4b2af19ad92a
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SnapToSurface/Editor/SnapToSurface.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 |
4 | namespace UnityToolbag
5 | {
6 | public class SnapToSurface : EditorWindow
7 | {
8 | void OnGUI()
9 | {
10 | using (new HorizontalBlock()) {
11 | if (GUILayout.Button("X")) {
12 | Drop(new Vector3(1, 0, 0));
13 | }
14 | if (GUILayout.Button("Y")) {
15 | Drop(new Vector3(0, 1, 0));
16 | }
17 | if (GUILayout.Button("Z")) {
18 | Drop(new Vector3(0, 0, 1));
19 | }
20 | }
21 |
22 | using (new HorizontalBlock()) {
23 | if (GUILayout.Button("-X")) {
24 | Drop(new Vector3(-1, 0, 0));
25 | }
26 | if (GUILayout.Button("-Y")) {
27 | Drop(new Vector3(0, -1, 0));
28 | }
29 | if (GUILayout.Button("-Z")) {
30 | Drop(new Vector3(0, 0, -1));
31 | }
32 | }
33 | }
34 |
35 | [MenuItem("Window/Snap to Surface")]
36 | static void ShowWindow()
37 | {
38 | var window = EditorWindow.GetWindow();
39 | window.title = "Snap To Surface";
40 | }
41 |
42 | static void Drop(Vector3 dir)
43 | {
44 | foreach (GameObject go in Selection.gameObjects) {
45 | // If the object has a collider we can do a nice sweep test for accurate placement
46 | var collider = go.GetComponent();
47 | if (collider != null && !(collider is CharacterController)) {
48 | // Figure out if we need a temp rigid body and add it if needed
49 | var rigidBody = go.GetComponent();
50 | bool addedRigidBody = false;
51 | if (rigidBody == null) {
52 | rigidBody = go.AddComponent();
53 | addedRigidBody = true;
54 | }
55 |
56 | // Sweep the rigid body downwards and, if we hit something, move the object the distance
57 | RaycastHit hit;
58 | if (rigidBody.SweepTest(dir, out hit)) {
59 | go.transform.position += dir * hit.distance;
60 | }
61 |
62 | // If we added a rigid body for this test, remove it now
63 | if (addedRigidBody) {
64 | DestroyImmediate(rigidBody);
65 | }
66 | }
67 | // Without a collider, we do a simple raycast from the transform
68 | else {
69 | // Change the object to the "ignore raycast" layer so it doesn't get hit
70 | int savedLayer = go.layer;
71 | go.layer = 2;
72 |
73 | // Do the raycast and move the object down if it hit something
74 | RaycastHit hit;
75 | if (Physics.Raycast(go.transform.position, dir, out hit)) {
76 | go.transform.position = hit.point;
77 | }
78 |
79 | // Restore layer for the object
80 | go.layer = savedLayer;
81 | }
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/SnapToSurface/Editor/SnapToSurface.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c838b4d5b94af49daaa372dc555054e7
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SnapToSurface/README.md:
--------------------------------------------------------------------------------
1 | Snap To Surface
2 | ===
3 |
4 | This adds an option to the Window menu to open up a little editor utility that uses Unity's physics to snap objects to surfaces in any of the six primary axis. Great for aligning objects with the ground or a wall.
5 |
--------------------------------------------------------------------------------
/SnapToSurface/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 35efd26e6fa464a389291d5c6c734148
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/SortingLayer.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5f36971a9810e42c69cde6bf80d69a01
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SortingLayer/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 08fb0a7540311494dab5a5cbaadaad39
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerDrawer.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System;
4 |
5 | namespace UnityToolbag
6 | {
7 | [CustomPropertyDrawer(typeof(SortingLayerAttribute))]
8 | public class SortingLayerDrawer : PropertyDrawer
9 | {
10 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
11 | {
12 | var sortingLayerNames = SortingLayerHelper.sortingLayerNames;
13 | if (property.propertyType != SerializedPropertyType.Integer) {
14 | EditorGUI.HelpBox(position, string.Format("{0} is not an integer but has [SortingLayer].", property.name), MessageType.Error);
15 | }
16 | else if (sortingLayerNames != null) {
17 | EditorGUI.BeginProperty(position, label, property);
18 |
19 | // Look up the layer name using the current layer ID
20 | string oldName = SortingLayerHelper.GetSortingLayerNameFromID(property.intValue);
21 |
22 | // Use the name to look up our array index into the names list
23 | int oldLayerIndex = Array.IndexOf(sortingLayerNames, oldName);
24 |
25 | // Show the popup for the names
26 | int newLayerIndex = EditorGUI.Popup(position, label.text, oldLayerIndex, sortingLayerNames);
27 |
28 | // If the index changes, look up the ID for the new index to store as the new ID
29 | if (newLayerIndex != oldLayerIndex) {
30 | property.intValue = SortingLayerHelper.GetSortingLayerIDForIndex(newLayerIndex);
31 | }
32 |
33 | EditorGUI.EndProperty();
34 | }
35 | else {
36 | EditorGUI.BeginProperty(position, label, property);
37 | int newValue = EditorGUI.IntField(position, label.text, property.intValue);
38 | if (newValue != property.intValue) {
39 | property.intValue = newValue;
40 | }
41 | EditorGUI.EndProperty();
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8b91c91bd332d41ce83f4fa342476942
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerExposedEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 | using UnityEditor;
4 |
5 | namespace UnityToolbag
6 | {
7 | [CustomEditor(typeof(SortingLayerExposed))]
8 | public class SortingLayerExposedEditor : Editor
9 | {
10 | public override void OnInspectorGUI()
11 | {
12 | // Get the renderer from the target object
13 | var renderer = (target as SortingLayerExposed).gameObject.GetComponent();
14 |
15 | // If there is no renderer, we can't do anything
16 | if (!renderer) {
17 | EditorGUILayout.HelpBox("SortingLayerExposed must be added to a game object that has a renderer.", MessageType.Error);
18 | return;
19 | }
20 |
21 | var sortingLayerNames = SortingLayerHelper.sortingLayerNames;
22 |
23 | // If we have the sorting layers array, we can make a nice dropdown. For stability's sake, if the array is null
24 | // we just use our old logic. This makes sure the script works in some fashion even if Unity changes the name of
25 | // that internal field we reflected.
26 | if (sortingLayerNames != null) {
27 | // Look up the layer name using the current layer ID
28 | string oldName = SortingLayerHelper.GetSortingLayerNameFromID(renderer.sortingLayerID);
29 |
30 | // Use the name to look up our array index into the names list
31 | int oldLayerIndex = Array.IndexOf(sortingLayerNames, oldName);
32 |
33 | // Show the popup for the names
34 | int newLayerIndex = EditorGUILayout.Popup("Sorting Layer", oldLayerIndex, sortingLayerNames);
35 |
36 | // If the index changes, look up the ID for the new index to store as the new ID
37 | if (newLayerIndex != oldLayerIndex) {
38 | Undo.RecordObject(renderer, "Edit Sorting Layer");
39 | renderer.sortingLayerID = SortingLayerHelper.GetSortingLayerIDForIndex(newLayerIndex);
40 | EditorUtility.SetDirty(renderer);
41 | }
42 | }
43 | else {
44 | // Expose the sorting layer name
45 | string newSortingLayerName = EditorGUILayout.TextField("Sorting Layer Name", renderer.sortingLayerName);
46 | if (newSortingLayerName != renderer.sortingLayerName) {
47 | Undo.RecordObject(renderer, "Edit Sorting Layer Name");
48 | renderer.sortingLayerName = newSortingLayerName;
49 | EditorUtility.SetDirty(renderer);
50 | }
51 |
52 | // Expose the sorting layer ID
53 | int newSortingLayerId = EditorGUILayout.IntField("Sorting Layer ID", renderer.sortingLayerID);
54 | if (newSortingLayerId != renderer.sortingLayerID) {
55 | Undo.RecordObject(renderer, "Edit Sorting Layer ID");
56 | renderer.sortingLayerID = newSortingLayerId;
57 | EditorUtility.SetDirty(renderer);
58 | }
59 | }
60 |
61 | // Expose the manual sorting order
62 | int newSortingLayerOrder = EditorGUILayout.IntField("Sorting Layer Order", renderer.sortingOrder);
63 | if (newSortingLayerOrder != renderer.sortingOrder) {
64 | Undo.RecordObject(renderer, "Edit Sorting Order");
65 | renderer.sortingOrder = newSortingLayerOrder;
66 | EditorUtility.SetDirty(renderer);
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerExposedEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0023ebe85cec44f38a53da0ad2084ad8
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerHelper.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEditor;
3 | using System;
4 | using System.Reflection;
5 |
6 | namespace UnityToolbag
7 | {
8 | // Helpers used by the different sorting layer classes.
9 | [InitializeOnLoad]
10 | public static class SortingLayerHelper
11 | {
12 | private static Type _utilityType;
13 | private static PropertyInfo _sortingLayerNamesProperty;
14 | private static MethodInfo _getSortingLayerIDMethod;
15 |
16 | static SortingLayerHelper()
17 | {
18 | _utilityType = Type.GetType("UnityEditorInternal.InternalEditorUtility, UnityEditor");
19 | _sortingLayerNamesProperty = _utilityType.GetProperty("sortingLayerNames", BindingFlags.Static | BindingFlags.NonPublic);
20 |
21 | // Unity 5.0 calls this "GetSortingLayerUniqueID" but in 4.x it was "GetSortingLayerUserID".
22 | _getSortingLayerIDMethod = _utilityType.GetMethod("GetSortingLayerUniqueID", BindingFlags.Static | BindingFlags.NonPublic);
23 | if (_getSortingLayerIDMethod == null) {
24 | _getSortingLayerIDMethod = _utilityType.GetMethod("GetSortingLayerUserID", BindingFlags.Static | BindingFlags.NonPublic);
25 | }
26 | }
27 |
28 | // Gets an array of sorting layer names.
29 | // Since this uses reflection, callers should check for 'null' which will be returned if the reflection fails.
30 | public static string[] sortingLayerNames
31 | {
32 | get
33 | {
34 | if (_sortingLayerNamesProperty == null) {
35 | return null;
36 | }
37 |
38 | return _sortingLayerNamesProperty.GetValue(null, null) as string[];
39 | }
40 | }
41 |
42 | // Given the ID of a sorting layer, returns the sorting layer's name
43 | public static string GetSortingLayerNameFromID(int id)
44 | {
45 | string[] names = sortingLayerNames;
46 | if (names == null) {
47 | return null;
48 | }
49 |
50 | for (int i = 0; i < names.Length; i++) {
51 | if (GetSortingLayerIDForIndex(i) == id) {
52 | return names[i];
53 | }
54 | }
55 |
56 | return null;
57 | }
58 |
59 | // Given the name of a sorting layer, returns the ID.
60 | public static int GetSortingLayerIDForName(string name)
61 | {
62 | string[] names = sortingLayerNames;
63 | if (names == null) {
64 | return 0;
65 | }
66 |
67 | return GetSortingLayerIDForIndex(Array.IndexOf(names, name));
68 | }
69 |
70 | // Helper to convert from a sorting layer INDEX to a sorting layer ID. These are not the same thing.
71 | // IDs are based on the order in which layers were created and do not change when reordering the layers.
72 | // Thankfully there is a private helper we can call to get the ID for a layer given its index.
73 | public static int GetSortingLayerIDForIndex(int index)
74 | {
75 | if (_getSortingLayerIDMethod == null) {
76 | return 0;
77 | }
78 |
79 | return (int)_getSortingLayerIDMethod.Invoke(null, new object[] { index });
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/SortingLayer/Editor/SortingLayerHelper.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ff736edc597c34343ba66affa329e33b
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SortingLayer/README.md:
--------------------------------------------------------------------------------
1 | Sorting Layer
2 | ===
3 |
4 | Unity 4.3 added sorting layers and manual sorting orders to all renderers, however only the sprite renderer exposes the values in the inspector. If you're making a 2D game and want to use a text mesh or a standard MeshRenderer, your only option is to adjust sorting layers in code. This folder provides a couple ways of nicely accessing sorting layers on non-sprite objects:
5 |
6 |
7 | SortingLayerExposed
8 | ---
9 |
10 | 
11 |
12 | This basic component+editor combo lets you change the sorting properties of _any_ renderer simply by putting the SortingLayerExposed component on the same object. The custom editor will provide you with the UI for choosing the sorting layer and sorting layer order for the renderer attached to the object.
13 |
14 | SortingLayerAttribute
15 | ---
16 |
17 | _This attribute/property drawer was adapted from [ChemiKhazi's pull request](https://github.com/nickgravelyn/UnityToolbag/pull/1)_.
18 |
19 | If you want to change an object's sorting layer at runtime but want to configure it in the inspector, this is the better option. Using attributes, you can have any regular integer property show up as a sorting layer in the Inspector. Example usage:
20 |
21 | public class SortLayerTest : MonoBehaviour {
22 | [UnityToolbag.SortingLayer]
23 | public int sortLayer1;
24 | }
25 |
26 | This will appear in the inspector with a nice drop down for selecting the sorting layer:
27 |
28 | 
29 |
30 | Then you can use that sorting layer to update a renderer at runtime:
31 |
32 | renderer.sortingLayerID = sortLayer1;
33 |
34 | This is good, for example, to have an object that starts on one layer (say, the background) but later needs to be moved to the foreground layer.
35 |
--------------------------------------------------------------------------------
/SortingLayer/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e92ef8ec88a264863a8d091ed7fc6257
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/SortingLayer/Readme_SortingLayerAttribute.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianMo/UnityToolbag/54867ab617825ab10a9844563ce1dacb9fe6c26b/SortingLayer/Readme_SortingLayerAttribute.png
--------------------------------------------------------------------------------
/SortingLayer/Readme_SortingLayerAttribute.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ebd306056e2254aa39614888e119c3eb
3 | TextureImporter:
4 | serializedVersion: 2
5 | mipmaps:
6 | mipMapMode: 0
7 | enableMipMap: 1
8 | linearTexture: 0
9 | correctGamma: 0
10 | fadeOut: 0
11 | borderMipMap: 0
12 | mipMapFadeDistanceStart: 1
13 | mipMapFadeDistanceEnd: 3
14 | bumpmap:
15 | convertToNormalMap: 0
16 | externalNormalMap: 0
17 | heightScale: .25
18 | normalMapFilter: 0
19 | isReadable: 0
20 | grayScaleToAlpha: 0
21 | generateCubemap: 0
22 | seamlessCubemap: 0
23 | textureFormat: -1
24 | maxTextureSize: 1024
25 | textureSettings:
26 | filterMode: -1
27 | aniso: -1
28 | mipBias: -1
29 | wrapMode: -1
30 | nPOTScale: 1
31 | lightmap: 0
32 | compressionQuality: 50
33 | spriteMode: 0
34 | spriteExtrude: 1
35 | spriteMeshType: 1
36 | alignment: 0
37 | spritePivot: {x: .5, y: .5}
38 | spritePixelsToUnits: 100
39 | alphaIsTransparency: 0
40 | textureType: -1
41 | buildTargetSettings: []
42 | spriteSheet:
43 | sprites: []
44 | spritePackingTag:
45 | userData:
46 |
--------------------------------------------------------------------------------
/SortingLayer/Readme_SortingLayerExposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianMo/UnityToolbag/54867ab617825ab10a9844563ce1dacb9fe6c26b/SortingLayer/Readme_SortingLayerExposed.png
--------------------------------------------------------------------------------
/SortingLayer/Readme_SortingLayerExposed.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0554f8a4ba2d84913ac1c385207b1425
3 | TextureImporter:
4 | serializedVersion: 2
5 | mipmaps:
6 | mipMapMode: 0
7 | enableMipMap: 1
8 | linearTexture: 0
9 | correctGamma: 0
10 | fadeOut: 0
11 | borderMipMap: 0
12 | mipMapFadeDistanceStart: 1
13 | mipMapFadeDistanceEnd: 3
14 | bumpmap:
15 | convertToNormalMap: 0
16 | externalNormalMap: 0
17 | heightScale: .25
18 | normalMapFilter: 0
19 | isReadable: 0
20 | grayScaleToAlpha: 0
21 | generateCubemap: 0
22 | seamlessCubemap: 0
23 | textureFormat: -1
24 | maxTextureSize: 1024
25 | textureSettings:
26 | filterMode: -1
27 | aniso: -1
28 | mipBias: -1
29 | wrapMode: -1
30 | nPOTScale: 1
31 | lightmap: 0
32 | compressionQuality: 50
33 | spriteMode: 0
34 | spriteExtrude: 1
35 | spriteMeshType: 1
36 | alignment: 0
37 | spritePivot: {x: .5, y: .5}
38 | spritePixelsToUnits: 100
39 | alphaIsTransparency: 0
40 | textureType: -1
41 | buildTargetSettings: []
42 | spriteSheet:
43 | sprites: []
44 | spritePackingTag:
45 | userData:
46 |
--------------------------------------------------------------------------------
/SortingLayer/SortingLayerAttribute.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace UnityToolbag
4 | {
5 | // Used to mark an 'int' field as a sorting layer so it will use the SortingLayerDrawer to display in the Inspector window.
6 | public class SortingLayerAttribute : PropertyAttribute
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SortingLayer/SortingLayerAttribute.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c39b83cf4b12b4d63ab924ea39c34f2d
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/SortingLayer/SortingLayerExposed.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace UnityToolbag
4 | {
5 | // Component does nothing; editor script does all the magic
6 | [AddComponentMenu("UnityToolbag/Sorting Layer Exposed")]
7 | public class SortingLayerExposed : MonoBehaviour
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SortingLayer/SortingLayerExposed.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 188c61358730b4e259a3aed7fefaa890
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ac53015c1fcbd4502a28f47dcb5cee0f
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/README.md:
--------------------------------------------------------------------------------
1 | Time Scale Independent Update
2 | ===
3 |
4 | This is my take on the code shared by [Asteroid Base](http://www.asteroidbase.com/) in their blog post [Pausing Without Pausing.](http://www.asteroidbase.com/devlog/9-pausing-without-pausing/). Here's an exerpt to give some context, but read their whole post for a full explanation:
5 |
6 | > The simplest approach to pausing your game in Unity is to set `Time.timeScale = 0`. While the time scale is 0, `Update` methods in your scripts will still called, but `Time.deltaTime` will always return 0. This works well if you want to pause all on-screen action, but it is severely limiting if you need animated menus or overlays, since `Time.timeScale = 0` also pauses animations and particle systems.
7 | >
8 | > We first encountered this limitation when we were trying to implement a world map in Lovers. When the player enters the ship’s map station, we display a overlay of the current level. Since the map obstructs the ship and, as such, inhibits gameplay, we needed to pause the game while the display is visible. However, a completely static map screen would make it difficult to convey information (and also look pretty dull). In order to achieve our goal we needed a separate way to track how much time has elapsed since the last update loop.
9 | >
10 | > It turns out that `Time.realtimeSinceStartup` is the ideal mechanism for this. As its name implies, `Time.realtimeSinceStartup` uses the system clock to track how much time has elapsed since the game was started, independent of any time scale manipulation you may be doing. By tracking the previous update’s `Time.realtimeSinceStartup`, we can calculate a good approximation of the delta time since the last frame.
11 |
12 | Instead of using `Time.realtimeSinceStartup`, I instead use the `Stopwatch.GetTimestamp()` and `Stopwatch.Frequency` to calculate the elapsed time. This approach uses longs for the main time tracking, converting to float only for the delta, in order to avoid issues with floating point values representing long running time. From [http://www.altdevblogaday.com/2012/02/05/dont-store-that-in-a-float/](http://www.altdevblogaday.com/2012/02/05/dont-store-that-in-a-float/):
13 |
14 | > Therefore, if our game timer starts at zero and we store time in a float then after a minute the best precision we can get from our timer is 3.8 microseconds. After our game has been running for an hour our best precision drops to 0.24 milliseconds. After our game has been running for a day our precision drops to 7.8 milliseconds, and after a week our precision drops to 62.5 milliseconds.
15 |
16 | While a game running for a day is farfetched, the fact is that using a float for that time means that by the end of the first day your accuracy has dropped to 1/120 of a second. For games that may have to pass long running soak tests (such as for consoles), this could be problematic. Since Unity exposes `Time.realtimeSinceStartup` as a float, we can't get any better resolution from them so we have to use other functionality to achieve this, in this case the `Stopwatch` class from .NET.
17 |
18 | Additionally the following smaller changes were made:
19 |
20 | - Use of cached components in the animation and particle system components for improved performance.
21 | - Additional callback signature for the animation component.
22 | - Made animation component public variables private with [SerializeField] so they're exposed to the Inspector but not available in scripting because options for an animation to play on startup aren't particularly useful in script and do nothing once the script has started.
23 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a1e19c54b49354e9380aa5d2af5f27b7
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentAnimation.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code in this file based on code provided by Asteroid Base in their blog:
3 | * http://www.asteroidbase.com/devlog/9-pausing-without-pausing/
4 | */
5 |
6 | using UnityEngine;
7 | using System;
8 |
9 | namespace UnityToolbag
10 | {
11 | ///
12 | /// A component that can be attached to an object with an Animation to update it using real-time,
13 | /// to allow it to continue updating when Time.timeScale is set to 0.
14 | ///
15 | [AddComponentMenu("UnityToolbag/Time Scale Independent Animation")]
16 | public class TimeScaleIndependentAnimation : TimeScaleIndependentUpdate
17 | {
18 | // Cache the animation so we can minimize calls to GetComponent which happen
19 | // implicitly each time you call the MonoBehaviour.animation property.
20 | private Animation _animation;
21 |
22 | private AnimationState _currentState;
23 | private Action _callback0;
24 | private Action _callback1;
25 | private float _elapsedTime;
26 | private bool _isPlaying;
27 |
28 | [SerializeField]
29 | private bool _playOnStart;
30 |
31 | [SerializeField]
32 | private string _playOnStartStateName;
33 |
34 | void Start()
35 | {
36 | if (_playOnStart) {
37 | Play(_playOnStartStateName);
38 | }
39 | }
40 |
41 | protected override void Update()
42 | {
43 | // Always update the base first when subclassing TimeScaleIndependentUpdate
44 | base.Update();
45 |
46 | if (_isPlaying) {
47 | // If for some reason the state goes away (maybe the animation is destroyed), log a warning and stop playing.
48 | if (!_currentState) {
49 | Debug.LogWarning("Animation is playing but the AnimationState isn't valid.", this);
50 | Stop();
51 | return;
52 | }
53 |
54 | _elapsedTime += deltaTime;
55 | _currentState.normalizedTime = _elapsedTime / _currentState.length;
56 |
57 | if (_elapsedTime >= _currentState.length) {
58 | _isPlaying = false;
59 |
60 | if (_currentState.wrapMode == WrapMode.Loop) {
61 | Play(_currentState.name);
62 | }
63 | else if (_callback0 != null) {
64 | _callback0();
65 | }
66 | else if (_callback1 != null) {
67 | _callback1(this);
68 | }
69 | }
70 | }
71 | }
72 |
73 | ///
74 | /// Plays a given animation.
75 | ///
76 | /// The name of the animation to play.
77 | public void Play(string name)
78 | {
79 | // Validate that we have an animation
80 | if (!_animation) {
81 | _animation = GetComponent();
82 |
83 | if (!_animation) {
84 | Debug.LogWarning("No valid animation attached to object.", this);
85 | return;
86 | }
87 | }
88 |
89 | // Get and validate the animation state
90 | var state = GetComponent()[name];
91 | if (!state) {
92 | Debug.LogWarning(string.Format("No animation state named '{0}' found.", name), this);
93 | return;
94 | }
95 |
96 | _elapsedTime = 0f;
97 | _currentState = state;
98 | _currentState.normalizedTime = 0;
99 | _currentState.enabled = true;
100 | _currentState.weight = 1;
101 | _isPlaying = true;
102 |
103 | _callback0 = null;
104 | _callback1 = null;
105 | }
106 |
107 | ///
108 | /// Plays a given animation with an action to invoke when the animation completes.
109 | ///
110 | /// The name of the animation to play.
111 | /// The action to invoke when the animation completes.
112 | public void Play(string name, Action callback)
113 | {
114 | Play(name);
115 | _callback0 = callback;
116 | _callback1 = null;
117 | }
118 |
119 | ///
120 | /// Plays a given animation with an action to invoke when the animation completes.
121 | ///
122 | /// The name of the animation to play.
123 | /// The action to invoke when the animation completes.
124 | public void Play(string name, Action callback)
125 | {
126 | Play(name);
127 | _callback0 = null;
128 | _callback1 = callback;
129 | }
130 |
131 | ///
132 | /// Stops playback of the animation.
133 | ///
134 | public void Stop()
135 | {
136 | _elapsedTime = 0f;
137 | _currentState = null;
138 | _callback0 = null;
139 | _callback1 = null;
140 | _isPlaying = false;
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentAnimation.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7ef8d8a8a2cf34c23b712cd9d7e105ac
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentParticleSystem.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code in this file based on code provided by Asteroid Base in their blog:
3 | * http://www.asteroidbase.com/devlog/9-pausing-without-pausing/
4 | */
5 |
6 | using UnityEngine;
7 |
8 | namespace UnityToolbag
9 | {
10 | ///
11 | /// A component that can be attached to an object with a ParticleSystem to update it using real-time,
12 | /// to allow it to continue updating when Time.timeScale is set to 0.
13 | ///
14 | [AddComponentMenu("UnityToolbag/Time Scale Independent ParticleSystem")]
15 | public class TimeScaleIndependentParticleSystem : TimeScaleIndependentUpdate
16 | {
17 | // Cache the particle system so we can minimize calls to GetComponent which happen
18 | // implicitly each time you call the MonoBehaviour.particleSystem property.
19 | private ParticleSystem _particleSystem;
20 |
21 | protected override void Update()
22 | {
23 | // Always update the base first when subclassing TimeScaleIndependentUpdate
24 | base.Update();
25 |
26 | // Update our particle system cache if our private field is null or the instance was destroyed.
27 | if (!_particleSystem) {
28 | _particleSystem = GetComponent();
29 |
30 | if (!_particleSystem) {
31 | Debug.LogWarning("No valid particle system attached to object.", this);
32 | return;
33 | }
34 | }
35 |
36 | // If we have a valid system, go ahead and simulate.
37 | _particleSystem.Simulate(deltaTime, true, false);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentParticleSystem.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 15dea6e4b77f746ddb40eef40decf466
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentUpdate.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code in this file based on code provided by Asteroid Base in their blog:
3 | * http://www.asteroidbase.com/devlog/9-pausing-without-pausing/
4 | */
5 |
6 | using UnityEngine;
7 | using System;
8 | using System.Collections;
9 | using System.Diagnostics;
10 |
11 | namespace UnityToolbag
12 | {
13 | ///
14 | /// A script that can be added to an object to allow for tracking elapsed time even when Time.timeScale is 0.
15 | ///
16 | public class TimeScaleIndependentUpdate : MonoBehaviour
17 | {
18 | ///
19 | /// Whether or not the game is paused. While having the scripts poll for this is nicer, this is still global
20 | /// to all objects and lets this script not require more dependencies than necessary.
21 | ///
22 | public static bool IsGamePaused;
23 |
24 | private long _previousTicks;
25 |
26 | ///
27 | /// Whether or not this instance pauses when IsGamePaused is set to true.
28 | ///
29 | public bool pauseWhenGameIsPaused = true;
30 |
31 | ///
32 | /// Gets the elapsed time.
33 | ///
34 | public float deltaTime { get; private set; }
35 |
36 | protected virtual void Awake()
37 | {
38 | _previousTicks = Stopwatch.GetTimestamp();
39 | }
40 |
41 | protected virtual void Update()
42 | {
43 | long currentTicks = Stopwatch.GetTimestamp();
44 | deltaTime = (float)(currentTicks - _previousTicks) / Stopwatch.Frequency;
45 | _previousTicks = currentTicks;
46 |
47 | if (pauseWhenGameIsPaused && IsGamePaused)
48 | {
49 | // If this update pauses with the game and the game has been marked as paused, disregard the delta time.
50 | deltaTime = 0;
51 | }
52 | else if (deltaTime < 0)
53 | {
54 | // It is possible (especially if this script is attached to an object that is created when the
55 | // scene is loaded) that the calculated delta time is less than zero. In that case, discard this update.
56 | UnityEngine.Debug.LogWarning(string.Format("Delta time less than zero, discarding (delta time was {0})", deltaTime));
57 | deltaTime = 0;
58 | }
59 | }
60 |
61 | ///
62 | /// An enumerator used to wait for a given amount of seconds using the real-time based delta time.
63 | ///
64 | /// The amount of time to wait, in seconds.
65 | /// An enumerator that can be used as a coroutine.
66 | public IEnumerator TimeScaleIndependentWaitForSeconds(float seconds)
67 | {
68 | float elapsedTime = 0;
69 | while (elapsedTime < seconds)
70 | {
71 | yield return null;
72 | elapsedTime += this.deltaTime;
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/TimeScaleIndependentUpdate/TimeScaleIndependentUpdate.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 854e7cc3c04a1414497a496c74b4d633
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: -1000
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/UnityConstants.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c44e9fd078d3c4ad8a9b2d5c888d628e
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/UnityConstants/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ef137d7693bd343e09048fe26d9c4ed3
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/UnityConstants/Editor/UnityConstantsGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using UnityEngine;
3 | using UnityEditor;
4 | using System.IO;
5 |
6 | namespace UnityToolbag
7 | {
8 | public static class UnityConstantsGenerator
9 | {
10 | [MenuItem("Edit/Generate UnityConstants.cs")]
11 | public static void Generate()
12 | {
13 | // Try to find an existing file in the project called "UnityConstants.cs"
14 | string filePath = string.Empty;
15 | foreach (var file in Directory.GetFiles(Application.dataPath, "*.cs", SearchOption.AllDirectories)) {
16 | if (Path.GetFileNameWithoutExtension(file) == "UnityConstants") {
17 | filePath = file;
18 | break;
19 | }
20 | }
21 |
22 | // If no such file exists already, use the save panel to get a folder in which the file will be placed.
23 | if (string.IsNullOrEmpty(filePath)) {
24 | string directory = EditorUtility.OpenFolderPanel("Choose location for UnityConstants.cs", Application.dataPath, "");
25 |
26 | // Canceled choose? Do nothing.
27 | if (string.IsNullOrEmpty(directory)) {
28 | return;
29 | }
30 |
31 | filePath = Path.Combine(directory, "UnityConstants.cs");
32 | }
33 |
34 | // Write out our file
35 | using (var writer = new StreamWriter(filePath)) {
36 | writer.WriteLine("// This file is auto-generated. Modifications are not saved.");
37 | writer.WriteLine();
38 | writer.WriteLine("namespace UnityConstants");
39 | writer.WriteLine("{");
40 |
41 | // Write out the tags
42 | writer.WriteLine(" public static class Tags");
43 | writer.WriteLine(" {");
44 | foreach (var tag in UnityEditorInternal.InternalEditorUtility.tags) {
45 | writer.WriteLine(" /// ");
46 | writer.WriteLine(" /// Name of tag '{0}'.", tag);
47 | writer.WriteLine(" /// ");
48 | writer.WriteLine(" public const string {0} = \"{1}\";", MakeSafeForCode(tag), tag);
49 | }
50 | writer.WriteLine(" }");
51 | writer.WriteLine();
52 |
53 | // Write out sorting layers
54 | var sortingLayerNames = SortingLayerHelper.sortingLayerNames;
55 | if (sortingLayerNames != null) {
56 | writer.WriteLine(" public static class SortingLayers");
57 | writer.WriteLine(" {");
58 | for (int i = 0; i < sortingLayerNames.Length; i++) {
59 | var name = sortingLayerNames[i];
60 | int id = SortingLayerHelper.GetSortingLayerIDForName(name);
61 | writer.WriteLine(" /// ");
62 | writer.WriteLine(" /// ID of sorting layer '{0}'.", name);
63 | writer.WriteLine(" /// ");
64 | writer.WriteLine(" public const int {0} = {1};", MakeSafeForCode(name), id);
65 | }
66 | writer.WriteLine(" }");
67 | writer.WriteLine();
68 | }
69 |
70 | // Write out layers
71 | writer.WriteLine(" public static class Layers");
72 | writer.WriteLine(" {");
73 | for (int i = 0; i < 32; i++) {
74 | string layer = UnityEditorInternal.InternalEditorUtility.GetLayerName(i);
75 | if (!string.IsNullOrEmpty(layer)) {
76 | writer.WriteLine(" /// ");
77 | writer.WriteLine(" /// Index of layer '{0}'.", layer);
78 | writer.WriteLine(" /// ");
79 | writer.WriteLine(" public const int {0} = {1};", MakeSafeForCode(layer), i);
80 | }
81 | }
82 | writer.WriteLine();
83 | for (int i = 0; i < 32; i++) {
84 | string layer = UnityEditorInternal.InternalEditorUtility.GetLayerName(i);
85 | if (!string.IsNullOrEmpty(layer)) {
86 | writer.WriteLine(" /// ");
87 | writer.WriteLine(" /// Bitmask of layer '{0}'.", layer);
88 | writer.WriteLine(" /// ");
89 | writer.WriteLine(" public const int {0}Mask = 1 << {1};", MakeSafeForCode(layer), i);
90 | }
91 | }
92 | writer.WriteLine(" }");
93 | writer.WriteLine();
94 |
95 | // Write out scenes
96 | writer.WriteLine(" public static class Scenes");
97 | writer.WriteLine(" {");
98 | for (int i = 0; i < EditorBuildSettings.scenes.Length; i++) {
99 | string scene = Path.GetFileNameWithoutExtension(EditorBuildSettings.scenes[i].path);
100 | writer.WriteLine(" /// ");
101 | writer.WriteLine(" /// Name of '{0}'.", scene);
102 | writer.WriteLine(" /// ");
103 | writer.WriteLine(" public const int {0} = {1};", MakeSafeForCode(scene), i);
104 | }
105 | writer.WriteLine(" }");
106 | writer.WriteLine("}");
107 | writer.WriteLine();
108 | }
109 |
110 | // Refresh
111 | AssetDatabase.Refresh();
112 | }
113 |
114 | private static string MakeSafeForCode(string str)
115 | {
116 | str = Regex.Replace(str, "[^a-zA-Z0-9_]", "_", RegexOptions.Compiled);
117 | if (char.IsDigit(str[0])) {
118 | str = "_" + str;
119 | }
120 | return str;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/UnityConstants/Editor/UnityConstantsGenerator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 85f4c98cc9a744b329b6ff2698b0202a
3 | MonoImporter:
4 | serializedVersion: 2
5 | defaultReferences: []
6 | executionOrder: 0
7 | icon: {instanceID: 0}
8 | userData:
9 |
--------------------------------------------------------------------------------
/UnityConstants/README.md:
--------------------------------------------------------------------------------
1 | UnityConstants
2 | ===
3 |
4 | UnityConstants is a code generator that produces a single file called `UnityConstants.cs` containing values for items you may modify in the editor. This tool currently generates constants for the following:
5 |
6 | - Tags
7 | - Sorting Layers
8 | - Layers
9 | - Scenes in the build configuration
10 |
11 | You can run this tool by choosing `Generate UnityConstants.cs` from the `Edit` menu. When you first run the command it will prompt you for a directory in which to save `UnityConstants.cs`. Each time after this, it will find that file and replace it, making it very quick to re-run the process.
12 |
13 | This is an example of what you'll see in `UnityConstants.cs`:
14 |
15 | namespace UnityConstants
16 | {
17 | public static class Tags
18 | {
19 | public const string Untagged = "Untagged";
20 | public const string Respawn = "Respawn";
21 | public const string Finish = "Finish";
22 | public const string EditorOnly = "EditorOnly";
23 | public const string MainCamera = "MainCamera";
24 | public const string Player = "Player";
25 | public const string GameController = "GameController";
26 | public const string Test = "Test";
27 | public const string AnotherTest = "Another Test";
28 | public const string _3Testing = "3 Testing";
29 | }
30 |
31 | public static class SortingLayers
32 | {
33 | public const int Default = 0;
34 | public const int Second = 1;
35 | public const int NewLayer3 = 3;
36 | public const int Another = 2;
37 | public const int NewLayer5 = 5;
38 | public const int Foreground = 7;
39 | }
40 |
41 | public static class Layers
42 | {
43 | public const int Default = 0;
44 | public const int TransparentFX = 1;
45 | public const int IgnoreRaycast = 2;
46 | public const int Water = 4;
47 | public const int Layer8 = 8;
48 | public const int Layer12 = 12;
49 | }
50 |
51 | public static class Scenes
52 | {
53 | public const int TestScene = 0;
54 | public const int TestScene2 = 1;
55 | public const int GameSaveSystemDemo = 2;
56 | }
57 | }
58 |
59 | UnityConstants does depend on the `SortingLayerHelper` class from the [SortingLayer](https://github.com/nickgravelyn/UnityToolbag/tree/master/SortingLayer) feature in order to get the names and IDs of the sorting layers.
60 |
--------------------------------------------------------------------------------
/UnityConstants/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 93f5aeced682a4a9fab07a08b13f4f5d
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------
/UnityLock.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: eaf27d3152f4578478a755c75c064e9d
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/UnityLock/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9bd9b5d56ac944f12be79dec9c9791fc
3 | folderAsset: yes
4 | DefaultImporter:
5 | userData:
6 |
--------------------------------------------------------------------------------
/UnityLock/Editor/UnityLock.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace UnityToolbag
7 | {
8 | [InitializeOnLoad]
9 | public class UnityLock : EditorWindow
10 | {
11 | private const string LockMenuItem = "GameObject/UnityLock/Lock GameObject";
12 | private const string LockRecursivelyMenuItem = "GameObject/UnityLock/Lock GameObject and Children %#l";
13 | private const string UnlockMenuItem = "GameObject/UnityLock/Unlock GameObject";
14 | private const string UnlockRecursivelyMenuItem = "GameObject/UnityLock/Unlock GameObject and Children %#u";
15 |
16 | private const string ShowIconPrefKey = "UnityLock_ShowIcon";
17 | private const string AddUndoRedoPrefKey = "UnityLock_EnableUndoRedo";
18 | private const string DisableSelectionPrefKey = "UnityLock_DisableSelection";
19 |
20 | static UnityLock()
21 | {
22 | if (!EditorPrefs.HasKey(ShowIconPrefKey)) {
23 | EditorPrefs.SetBool(ShowIconPrefKey, true);
24 | }
25 |
26 | if (!EditorPrefs.HasKey(AddUndoRedoPrefKey)) {
27 | EditorPrefs.SetBool(AddUndoRedoPrefKey, true);
28 | }
29 |
30 | if (!EditorPrefs.HasKey(DisableSelectionPrefKey)) {
31 | EditorPrefs.SetBool(DisableSelectionPrefKey, false);
32 | }
33 | }
34 |
35 | void OnGUI()
36 | {
37 | EditorGUILayout.BeginVertical();
38 |
39 | HierarchyIcon.drawingIcon = ShowPrefsBoolOption(ShowIconPrefKey, "Show lock icon in hierarchy");
40 | EditorGUILayout.HelpBox(
41 | "When enabled a small lock icon will appear in the hierarchy view for all locked objects.",
42 | MessageType.None);
43 |
44 | EditorGUILayout.Space();
45 |
46 | bool wasSelectionDisabled = EditorPrefs.GetBool(DisableSelectionPrefKey);
47 | bool isSelectionDisabled = ShowPrefsBoolOption(DisableSelectionPrefKey, "Disable selecting locked objects");
48 | EditorGUILayout.HelpBox(
49 | "When enabled locked objects will not be selectable in the scene view with a left click. Some objects can still be selected by using a selection rectangle; it doesn't appear to be possible to prevent this.\n\nObjects represented only with gizmos will not be drawn as gizmos aren't rendered when selection is disabled.",
50 | MessageType.None);
51 |
52 | if (wasSelectionDisabled != isSelectionDisabled) {
53 | ToggleSelectionOfLockedObjects(isSelectionDisabled);
54 | }
55 |
56 | EditorGUILayout.Space();
57 |
58 | ShowPrefsBoolOption(AddUndoRedoPrefKey, "Support undo/redo for lock and unlock");
59 | EditorGUILayout.HelpBox(
60 | "When enabled the lock and unlock operations will be properly inserted into the undo stack just like any other action.\n\nIf this is disabled the Undo button will never lock or unlock an object. This can cause other operations to silently fail, such as trying to undo a translation on a locked object.",
61 | MessageType.None);
62 |
63 | EditorGUILayout.EndVertical();
64 | }
65 |
66 | bool ShowPrefsBoolOption(string key, string name)
67 | {
68 | EditorGUILayout.BeginHorizontal();
69 |
70 | EditorGUILayout.LabelField(name, GUILayout.ExpandWidth(true));
71 | bool oldValue = EditorPrefs.GetBool(key);
72 | bool newValue = EditorGUILayout.Toggle(oldValue, GUILayout.Width(20));
73 | if (newValue != oldValue) {
74 | EditorPrefs.SetBool(key, newValue);
75 | }
76 |
77 | EditorGUILayout.EndHorizontal();
78 |
79 | return newValue;
80 | }
81 |
82 | [MenuItem("Window/UnityLock Preferences")]
83 | static void ShowPreferences()
84 | {
85 | var window = GetWindow();
86 | window.minSize = new Vector2(275, 300);
87 | window.Show();
88 | }
89 |
90 | private static void ToggleSelectionOfLockedObjects(bool disableSelection)
91 | {
92 | foreach (GameObject go in GameObject.FindObjectsOfType(typeof(GameObject))) {
93 | if ((go.hideFlags & HideFlags.NotEditable) == HideFlags.NotEditable) {
94 | foreach (Component comp in go.GetComponents(typeof(Component))) {
95 | if (!(comp is Transform)) {
96 | if (disableSelection) {
97 | comp.hideFlags |= HideFlags.NotEditable;
98 | comp.hideFlags |= HideFlags.HideInHierarchy;
99 | }
100 | else {
101 | comp.hideFlags &= ~HideFlags.NotEditable;
102 | comp.hideFlags &= ~HideFlags.HideInHierarchy;
103 | }
104 | }
105 | }
106 |
107 | EditorUtility.SetDirty(go);
108 | }
109 | }
110 | }
111 |
112 | private static void LockObject(GameObject go)
113 | {
114 | if (EditorPrefs.GetBool(AddUndoRedoPrefKey)) {
115 | Undo.RecordObject(go, "Lock Object");
116 | }
117 |
118 | go.hideFlags |= HideFlags.NotEditable;
119 |
120 | foreach (Component comp in go.GetComponents(typeof(Component))) {
121 | if (!(comp is Transform)) {
122 | if (EditorPrefs.GetBool(DisableSelectionPrefKey)) {
123 | comp.hideFlags |= HideFlags.NotEditable;
124 | comp.hideFlags |= HideFlags.HideInHierarchy;
125 | }
126 | }
127 | }
128 |
129 | EditorUtility.SetDirty(go);
130 | }
131 |
132 | private static void UnlockObject(GameObject go)
133 | {
134 | if (EditorPrefs.GetBool(AddUndoRedoPrefKey)) {
135 | Undo.RecordObject(go, "Unlock Object");
136 | }
137 |
138 | go.hideFlags &= ~HideFlags.NotEditable;
139 |
140 | foreach (Component comp in go.GetComponents(typeof(Component))) {
141 | if (!(comp is Transform)) {
142 | // Don't check pref key; no harm in removing flags that aren't there
143 | comp.hideFlags &= ~HideFlags.NotEditable;
144 | comp.hideFlags &= ~HideFlags.HideInHierarchy;
145 | }
146 | }
147 |
148 | EditorUtility.SetDirty(go);
149 | }
150 |
151 | [MenuItem(LockMenuItem)]
152 | static void Lock()
153 | {
154 | foreach (var go in Selection.gameObjects) {
155 | LockObject(go);
156 | }
157 | }
158 |
159 | [MenuItem(LockMenuItem, true)]
160 | static bool CanLock()
161 | {
162 | return Selection.gameObjects.Length > 0;
163 | }
164 |
165 | [MenuItem(LockRecursivelyMenuItem)]
166 | static void LockRecursively()
167 | {
168 | Stack objectsToLock = new Stack(Selection.gameObjects);
169 |
170 | while (objectsToLock.Count > 0) {
171 | var go = objectsToLock.Pop();
172 | LockObject(go);
173 |
174 | foreach (Transform childTransform in go.transform) {
175 | objectsToLock.Push(childTransform.gameObject);
176 | }
177 | }
178 | }
179 |
180 | [MenuItem(LockRecursivelyMenuItem, true)]
181 | static bool CanLockRecursively()
182 | {
183 | return Selection.gameObjects.Length > 0;
184 | }
185 |
186 | [MenuItem(UnlockMenuItem)]
187 | static void Unlock()
188 | {
189 | foreach (var go in Selection.gameObjects)
190 | {
191 | UnlockObject(go);
192 | }
193 | }
194 |
195 | [MenuItem(UnlockMenuItem, true)]
196 | static bool CanUnlock()
197 | {
198 | return Selection.gameObjects.Length > 0;
199 | }
200 |
201 | [MenuItem(UnlockRecursivelyMenuItem)]
202 | static void UnlockRecursively()
203 | {
204 | Stack objectsToUnlock = new Stack(Selection.gameObjects);
205 |
206 | while (objectsToUnlock.Count > 0) {
207 | var go = objectsToUnlock.Pop();
208 | UnlockObject(go);
209 |
210 | foreach (Transform childTransform in go.transform) {
211 | objectsToUnlock.Push(childTransform.gameObject);
212 | }
213 | }
214 | }
215 |
216 | [MenuItem(UnlockRecursivelyMenuItem, true)]
217 | static bool CanUnlockRecursively()
218 | {
219 | return Selection.gameObjects.Length > 0;
220 | }
221 |
222 | [InitializeOnLoad]
223 | private static class HierarchyIcon
224 | {
225 | private const float _iconSize = 16f;
226 | private static string _iconFile;
227 | private static Texture2D _icon;
228 | private static bool _drawingIcon;
229 |
230 | private static string iconFile
231 | {
232 | get
233 | {
234 | if (string.IsNullOrEmpty(_iconFile)) {
235 | var path = Directory.GetFiles(Application.dataPath, "UnityLockHierarchyIcon.png", SearchOption.AllDirectories)[0];
236 | _iconFile = "Assets" + path.Substring(Application.dataPath.Length).Replace('\\', '/');
237 | }
238 |
239 | return _iconFile;
240 | }
241 | }
242 |
243 | public static bool drawingIcon
244 | {
245 | get { return _drawingIcon; }
246 | set
247 | {
248 | if (_drawingIcon != value) {
249 | _drawingIcon = value;
250 |
251 | if (_drawingIcon) {
252 | EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemOnGUI;
253 | }
254 | else {
255 | EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemOnGUI;
256 | }
257 |
258 | EditorApplication.RepaintHierarchyWindow();
259 | }
260 | }
261 | }
262 |
263 | static HierarchyIcon()
264 | {
265 | drawingIcon = EditorPrefs.GetBool(UnityLock.ShowIconPrefKey);
266 | }
267 |
268 | private static void OnHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
269 | {
270 | var obj = EditorUtility.InstanceIDToObject(instanceID) as UnityEngine.Object;
271 | if (obj && (obj.hideFlags & HideFlags.NotEditable) == HideFlags.NotEditable) {
272 | if (!_icon) {
273 | _icon = AssetDatabase.LoadAssetAtPath(iconFile, typeof(Texture2D)) as Texture2D;
274 | }
275 |
276 | GUI.Box(
277 | new Rect(selectionRect.xMax - _iconSize, selectionRect.center.y - (_iconSize / 2f), _iconSize, _iconSize),
278 | _icon,
279 | GUIStyle.none);
280 | }
281 | }
282 | }
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/UnityLock/Editor/UnityLock.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1926963fb55f34c868cb02f60b569d2d
3 | labels:
4 | - lock
5 | - gameobject
6 | - noneditable
7 | MonoImporter:
8 | serializedVersion: 2
9 | defaultReferences: []
10 | executionOrder: 0
11 | icon: {instanceID: 0}
12 | userData:
13 |
--------------------------------------------------------------------------------
/UnityLock/Editor/UnityLockHierarchyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianMo/UnityToolbag/54867ab617825ab10a9844563ce1dacb9fe6c26b/UnityLock/Editor/UnityLockHierarchyIcon.png
--------------------------------------------------------------------------------
/UnityLock/Editor/UnityLockHierarchyIcon.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8bd761806dcbc349b2e3137295dd548
3 | TextureImporter:
4 | serializedVersion: 2
5 | mipmaps:
6 | mipMapMode: 0
7 | enableMipMap: 0
8 | linearTexture: 1
9 | correctGamma: 0
10 | fadeOut: 0
11 | borderMipMap: 0
12 | mipMapFadeDistanceStart: 1
13 | mipMapFadeDistanceEnd: 3
14 | bumpmap:
15 | convertToNormalMap: 0
16 | externalNormalMap: 0
17 | heightScale: .25
18 | normalMapFilter: 0
19 | isReadable: 0
20 | grayScaleToAlpha: 0
21 | generateCubemap: 0
22 | seamlessCubemap: 0
23 | textureFormat: -3
24 | maxTextureSize: 1024
25 | textureSettings:
26 | filterMode: -1
27 | aniso: 1
28 | mipBias: -1
29 | wrapMode: 1
30 | nPOTScale: 0
31 | lightmap: 0
32 | compressionQuality: 50
33 | spriteMode: 0
34 | spriteExtrude: 1
35 | spriteMeshType: 1
36 | alignment: 0
37 | spritePivot: {x: .5, y: .5}
38 | spritePixelsToUnits: 100
39 | alphaIsTransparency: 0
40 | textureType: 5
41 | buildTargetSettings: []
42 | spriteSheet:
43 | sprites: []
44 | spritePackingTag:
45 | userData:
46 |
--------------------------------------------------------------------------------
/UnityLock/README.md:
--------------------------------------------------------------------------------
1 | UnityLock
2 | ===
3 |
4 | UnityLock is a small utility that allows you to nearly completely lock any game objects in your scene from being edited.
5 | The idea is that you can identify pieces of your scene that you want to prevent from being accidentally moved or deleted,
6 | or perhaps from being edited entirely. These objects may be your primary scene geometry, some game manager type object, or
7 | anything else.
8 |
9 | UnityLock adds a "UnityLock" submenu to the GameObject menu in Unity. Inside this submenu are four menu items:
10 |
11 | - Lock Game Object
12 | - Lock Game Object and Children (Shortcut: Cmd-Shift-L for OS X or Control-Shift-L for Windows)
13 | - Unlock Game Object
14 | - Unlock Game Object and Children (Shortcut: Cmd-Shift-U for OS X or Control-Shift-U for Windows)
15 |
16 | These all behave the same way with regards to the objects, however you can choose whether you want to lock only the selected
17 | game object(s) or if you'd like to lock their children as well. Given that most people want to lock an entire hierarchy, those
18 | are the items that have the convenient keyboard shortcuts.
19 |
20 | Locking a game object has the follow effects in the editor:
21 |
22 | - Disables all 3D scene transform controls. Users cannot move, rotate, or scale the transform.
23 | - Disables all inspectors for all components on the object.
24 | - Prevents adding or removing components to/from the object.
25 | - Prevents the object from being deleted from the scene.
26 | - Prevents editing any top-level game object properties, such as the name, tag, layer, and static flags/toggle.
27 |
28 | Locking an object does NOT prevent users from changing the parent-child hierarchy of an object. This is a limitation of Unity
29 | as UnityLock sets the object to not be editable however Unity still allows the parent-child hierarchy to change in the editor.
30 |
31 | There are also some options available if you wish to customize the behavior of UnityLock. You will find a preferences window
32 | available in the Window menu which exposes all options and a description of what each one does.
33 |
--------------------------------------------------------------------------------
/UnityLock/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4e0b78bc889e24d8f911b9dc42810770
3 | DefaultImporter:
4 | userData:
5 |
--------------------------------------------------------------------------------