Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ScriptableObject + Cross-scene reference support #3

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ Tested in Unity 2020.1, 2019.3 and 2018.3. Unsure if it'll work in versions prio

![To Do](gif.gif)

• Attached as a **Component** on a **GameObject**. Can have as many as you want.<br />
• Uses a **Custom Inspector** based on **UnityEditorInternal.ReorderableList**. Easily reorganise tasks by dragging the left side of each element<br />
## Features :
• Attached as a **Component** (on a GameObject in Scene or Prefab), or as a **ScriptableObject** Asset. Can have as many as you want.<br />
• Uses a **Custom Inspector** based on **UnityEditorInternal.ReorderableList**. Easily reorganise tasks by dragging the left side of each element.<br />
• Title of ReorderableList can be edited.<br />
• Each task includes : **Completion Tickbox**, **Text Area**, and the option to link a **GameObject**, **Component** or **Asset** by dragging it onto the task. Cross-scene references are not supported and will be cleared when reloading the scene. Once an object is linked, it can be removed by using the **Object Field** that appears and selecting *None* at the top.<br />
• Each task includes : **Completion Tickbox**, **Text Area**, and the option to link a **GameObject**, **Component** or **Asset** by dragging it onto the task. Once an object is linked, it can be removed by using the **Object Field** that appears and selecting *None* at the top.<br />
• Completed tasks turn green. Use the *"Remove Completed Tasks"* button to remove all completed tasks.<br />
• Supports **Rich Text**, (but is disabled while editing the task, so you can see what you are writing)<br />
• Has **Undo / Redo** Support.<br />
• **Export** Tasks to a txt file (object references will be lost though).<br />
• **Import** Tasks from a txt file. Each line in the file will be added as a task. Lines starting with *"[Complete]"* are marked as completed.<br />
• **Export** Tasks to a txt file (object references will be lost though).<br />
• **Cross-Scene Object References** (and scene object references for ScriptableObject) is supported. Linking an object in the same scene will still use the direct object reference, but linking an object from a *different* scene will automatically create and use a hidden GameObject & SceneReferenceHandler script, which assigns a unique ID for the object. This handler is saved in the scene to keep references to objects in that scene, while the SceneAsset and ID is saved in the ToDo list. Cross-scene linked objects will show an asterisk on the task, and the object can only be obtained if the scene is loaded.<br />
<br />
## Known Bugs :
• Regular object references may be lost if a To Do list component is moved between scenes. (Cross-scene object references are unaffected)<br />
• Creating a Prefab from a GameObject containing the To Do list will update regular object references to the cross-scene method so they are not lost. However this can only occur if the To Do list is selected / visible in the inspector currently.<br />
<br />
@Cyanilux<br />
:)
439 changes: 368 additions & 71 deletions ToDo/Editor/ToDoEditor.cs

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions ToDo/SceneReferencesHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Cyan.ToDo {
/// <summary>
/// Stores references to objects in the scene, assigning a GUID.
/// That GUID can then be used to obtain the object, allowing for cross-scene references
/// (as well as references to scene objects for prefabs + ScriptableObject)
/// </summary>
[AddComponentMenu("Cyan/Scene References Handler", 2)]
public class SceneReferencesHandler : MonoBehaviour {

public static bool disallowSceneReferences = false;
/*
* Set to true if you want to disable this functionality.
* Storing scene references dirties the scene, so you might want to disable it if working with collab.
* Any existing references won't be lost, but GetSceneReferencesHandler(scene) will return null.
*
* If you want to remove/break existing references for a scene, use GameObject -> Cyan.ToDo -> Remove Scene References Handler
*/

#if UNITY_2020_1_OR_NEWER
[SerializeField] private SerializableDictionary<string, Object> objects = new SerializableDictionary<string, Object>();
#else
[SerializeField] private SerializableDictionary_StringObject objects = new SerializableDictionary_StringObject();
#endif

private void Reset() {
if (gameObject.hideFlags != HideFlags.HideInHierarchy) {
Debug.LogWarning("Cyan.ToDo : Adding a SceneReferences component to a scene manually is not advised. (see console for more info)\n" +
"The ToDo system automatically adds this component when required to handle scene object references " +
"for the ScriptableObject version of the To Do list, or cross-scene references for the MonoBehaviour version. " +
"Regardless of adding the component manually, SceneReferences.GetSceneReferencesHandler will always use the hidden one, " +
"or create a hidden one if it doesn't exist.");
}
}

/// <summary>
/// Obtains the SceneReferences component on a hidden GameObject inside the given Scene.
/// If createIfNull is true, and it doesn't exist, it will create and add it to the scene.
/// Otherwise returns null.
/// </summary>
public static SceneReferencesHandler GetSceneReferencesHandler(Scene scene, bool createIfNull = false) {
if (disallowSceneReferences) return null;
GameObject[] roots = scene.GetRootGameObjects();

SceneReferencesHandler sceneReferencesHandler = null;
for (int i = 0; i < roots.Length; i++) {
GameObject root = roots[i];
#if UNITY_2019_2_OR_NEWER
if (root.TryGetComponent(out sceneReferencesHandler)) {
return sceneReferencesHandler;
}
#else
sceneReferencesHandler = root.GetComponent<SceneReferencesHandler>();
if (sceneReferencesHandler != null) {
return sceneReferencesHandler;
}
#endif
}

// Create Scene Reference Handler in scene
if (createIfNull) {
GameObject obj = new GameObject();
obj.hideFlags = HideFlags.HideInHierarchy;
SceneManager.MoveGameObjectToScene(obj, scene);
obj.transform.SetSiblingIndex(0);
sceneReferencesHandler = obj.AddComponent<SceneReferencesHandler>();
}
return sceneReferencesHandler;
}

public string RegisterObject(Object obj) {
string key = GetKeyFromObject(obj);
if (key != null) return key;
System.Guid guid = System.Guid.NewGuid();
string id = guid.ToString();
objects.Add(id, obj);
return id;
}

public Object GetObjectFromID(string id) {
if (objects.TryGetValue(id, out Object obj)) {
return obj;
}
return null;
}

public string GetKeyFromObject(Object obj) {
foreach (KeyValuePair<string, Object> entry in objects) {
if (entry.Value == obj) {
return entry.Key;
}
}
return null;
}

}

}
11 changes: 11 additions & 0 deletions ToDo/SceneReferencesHandler.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions ToDo/SerializableDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using UnityEngine;

namespace Cyan.ToDo {

#if !UNITY_2020_1_OR_NEWER
// Unity can't serialise generic fields like "SerializableDictionary<string, Object> fieldName" (prior to 2020.1)
// But it can serialise a class that inherits a generic class, so we need this for previous versions :
[System.Serializable]
public class SerializableDictionary_StringObject : SerializableDictionary<string, Object> {

}
#endif

/// <summary>
/// A version of Dictionary which is serialisable by converting it to and from Lists
/// </summary>
[System.Serializable]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver {
// Note : TKey and TVaue must be Serializable

[SerializeField] private List<TKey> keys = new List<TKey>();
[SerializeField] private List<TValue> values = new List<TValue>();

public void OnBeforeSerialize() {
// Convert Dictionary to Lists, so Unity can Serialize it
keys.Clear();
values.Clear();
foreach (KeyValuePair<TKey, TValue> pair in this) {
keys.Add(pair.Key);
values.Add(pair.Value);
}
}

public void OnAfterDeserialize() {
// Convert Lists to Dictionary
Clear();
for (int i = 0; i < keys.Count; i++) {
Add(keys[i], values[i]);
}
}
}
}
11 changes: 11 additions & 0 deletions ToDo/SerializableDictionary.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 22 additions & 8 deletions ToDo/ToDo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,46 @@

namespace Cyan.ToDo {

/// <summary>
/// MonoBehaviour version of the To Do list
/// </summary>
[AddComponentMenu("Cyan/To Do", 1)]
public class ToDo : MonoBehaviour {

public string listName = "To Do";
public List<ToDoElement> list = new List<ToDoElement>();
public ToDoList list = new ToDoList();

public void OnDrawGizmos() { }

}

[System.Serializable]
public class ToDoList {

public string listName = "To Do";
public List<ToDoElement> tasks = new List<ToDoElement>();

public void RemoveCompleted() {
for (int i = list.Count - 1; i >= 0; i--) {
ToDoElement element = list[i];
for (int i = tasks.Count - 1; i >= 0; i--) {
ToDoElement element = tasks[i];
if (element.completed) {
list.RemoveAt(i);
tasks.RemoveAt(i);
}
}
}

}

[System.Serializable]
public class ToDoElement {
public bool completed = false;
public string text = "";
public Object objectReference;

public Object objectReference; // (if cross-scene reference, this is the SceneAsset instead, but that only works in-editor)
public string objectReferenceID; // ID for serializing cross-scene reference (see SceneReferences)
[System.NonSerialized] public Object tempObjectReference; // actual object for cross-scene reference

public bool editing = false;
[System.NonSerialized] public bool editing = false;

public ToDoElement() { }
}

}
17 changes: 17 additions & 0 deletions ToDo/ToDoSO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Cyan.ToDo {

/// <summary>
/// ScriptableObject version of the To Do list
/// </summary>
[CreateAssetMenu(fileName = "To Do", menuName = "To Do List (ScriptableObject)", order = 1)]
public class ToDoSO : ScriptableObject {

public ToDoList list = new ToDoList();

}

}
11 changes: 11 additions & 0 deletions ToDo/ToDoSO.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.