Create Scriptable Object with constant, Unique ID - c#

I use Scriptable Objects to create Items for my Unity Game. Let's say I create a Stick, a Sword and a Helmet. Now to use them, I need to drag them into a list and on game start, each item get's an ID assigned which is also the position in the list, as I simply loop through the list to assign ID's. Now I can access the item by using the index it has or, as shown in most of the tutorials about ScriptableObjects, we can then add the item and the id to a Dictionary to better access them.
Stick is 0, Sword is 1 and Helmet is 2. Now I decide to remove the sword with a patch so helmet gets the ID of 1. Now every savegame is broken as swords become helmets.
How can I handle assigning fixed ID's which wont change and wont be taken by another item until I really want to assign it again? I could of course hard-code the id for every ScriptableObject I create but as you can imagine, managing hard-coded IDs seems not like a good idea.

Here is my take on it :
Create a custom attribute "ScriptableObjectIdAttribute", you can use it in front of whatever you need to be an id.
Make a custom property drawer in order to initialize the id the first time you access the object in the inspector and also to make it non-editable by the inspector.
(Optional) Make a base class with that Id property and extend all your ScriptableObjects which need a unique Id from it.
BaseScriptableObject.cs :
using System;
using UnityEditor;
using UnityEngine;
public class ScriptableObjectIdAttribute : PropertyAttribute { }
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ScriptableObjectIdAttribute))]
public class ScriptableObjectIdDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
GUI.enabled = false;
if (string.IsNullOrEmpty(property.stringValue)) {
property.stringValue = Guid.NewGuid().ToString();
}
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
#endif
public class BaseScriptableObject : ScriptableObject {
[ScriptableObjectId]
public string Id;
}
Weapon.cs :
using System;
using UnityEngine;
[CreateAssetMenu(fileName = "weapon", menuName = "Items/Weapon")]
public class Weapon : BaseScriptableObject {
public int Attack;
public int Defence;
}
Result :

Create a string "ID" variable, check if it is empty when the Scriptable Objects Validates. If it is empty Create a GUID, if it is not, do nothing.
public string UID;
private void OnValidate()
{
#if UNITY_EDITOR
if (UID == "")
{
UID = GUID.Generate().ToString();
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
This is how i did it, it also sets the scriptable object to dirty so it does not reset when you restart the unity editor.
You can go a step further and make the UID field read only to prevent changing it by accident.

This is what I ended up using for a simple, duplicate safe method.
public class YourObject : ScriptableObject
{
public string UID;
private void OnValidate()
{
if (string.IsNullOrWhiteSpace(UID))
{
AssignNewUID();
}
}
private void Reset()
{
AssignNewUID();
}
public void AssignNewUID()
{
UID = System.Guid.NewGuid().ToString();
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
}

Related

How to assign GameObject to child for serialization

I want to assign each item I create with a GameObject of some sort and then when I create a new object (called Item) it will have the assigned GameObject as a child.
For that I have a class of a scriptable object which holds a public GameObject called "gameObj" within it:
public abstract class ItemObject : ScriptableObject
{
public int id;
public GameObject gameObj;
}
Then, in another class I want to have something of this sort:
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
this.gameObject.transform.GetChild(0) = item.gameObj; //WRONG CODE, NEED HELP HERE
}
}
The purpose is to set the gameObj from the given ItemObject.item as the GameObject for the GroundItem.
The purpose is in the end to have lots of scriptable items of all sorts (like bread, sword, stone etc) and each one will have a GameObject assigned to it, and once I create a new GroundItem game object I will simply assign the scriptable object item and its child will have the game object itself (which includes all the visuals, special scripts etc).
For reference, in the following link the person is doing this from minute 4 to minute 6, but with a sprite instead of a game object.
Anyone knows how it should be written? Is it even possible?
You would probably mean
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
if(!item) return;
if(!item.gameObj) return;
// make according object a child of this object
item.gameObj.transform.parent = transform;
// make it the firs child
item.gameObj.transform.SetSiblingIndex(0);
}
}
However, in general ScriptableObjects are assets and GameObjects are instances in the scene hierarchy -> you can't simply reference scene objects in assets, doing so anyway might lead to unexpected behavior.
Is there a good reason why this has to be done in ISerializationCallbackReceiver?
I think what you actually rather want to achieve would be e.g.
public class GroundItem : MonoBehaviour
{
public ItemObject item;
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
private ItemObject _lastItem;
// This is called whenever something is changed in this objects Inspector
// like e.g. item ;)
private void OnValidate()
{
// Delay the call in order to not get warnings for SendMessage
UnityEditor.EditorApplication.delayCall += DelayedOnValidate;
}
private void DelayedOnValidate()
{
// remove the callback since we want to be sure it is always added only once
UnityEditor.EditorApplication.delayCall -= DelayedOnValidate;
// if item hasn't changed nothing to do
if (item == _lastItem) return;
// otherwise first destroy current child if any
if (_lastItem && transform.childCount > 0)
{
if (Application.isPlaying) Destroy(transform.GetChild(0).gameObject);
else DestroyImmediate(transform.GetChild(0).gameObject);
}
// is an item referenced and does it even have a gameObject ?
if (item && item.gameObj)
{
// instantiate the new one as child of this object
var obj = Instantiate(item.gameObj, transform);
// set as first child (if needed)
obj.transform.SetSiblingIndex(0);
if (!Application.isPlaying)
{
// if in edit mode mark this object as dirty so it needs to be saved
UnityEditor.EditorUtility.SetDirty(gameObject);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
}
}
_lastItem = item;
}
#endif
}
Which would now look like

Need Help Creating an inventory system using a List of ScriptableObjects

SOLVED
thank you for taking the time to view my question.
I am currently working on a project using Unity3D and have ran into a bit of difficulty whilst creating the inventory system. Upon doing some research I have decided that it would be in my best interest to use a List of ScriptableObjects (Item) to create this inventory system due to the ease of management and the expand-ability of Lists.
Just as an example I have an Item titled "Skull." When the player points towards the skull my code prompts the user to press F to pick up the item which in turn should remove the item instance from the game-world and add an instance of that Item into the inventory.
The part that is causing me the most trouble is my InventoryEditor script which controls the RayCast feature I mentioned previously.
Here is the code in my InventoryEditor Script with the exception of Update and Start methods as they are working as intended
using UnityEngine;
public class InventoryEditor : MonoBehaviour
{
private bool isOpen = false;
public TMPro.TMP_Text itemIDTM;
public GameObject currentObject;
public GameObject inventoryUI;
new Inventory playerInventory;
public void CurrentObject()
{
RaycastHit itemHit;
Ray itemFinder = new Ray(this.transform.position, transform.TransformDirection(Vector3.forward));
if (Physics.Raycast(itemFinder, out itemHit))
{
if (itemHit.collider.tag == "Item")
{
// Rather than itemHit.collider.name I would prefer if it uses the Identifier of the
//Item ScriptableObject V
itemIDTM.text = "F - Pick up: " + itemHit.collider.name;
if (Input.GetKeyDown(KeyCode.F))
{
/*
I need to Add an Item When F is pressed
Something along the lines of:
playerInventory.AddItem(Item that is hit by RayCast);
*/
}
}
else
{
itemIDTM.text = "";
}
}
}
}
Here is my ScriptableObject which has not been causing me any issues
using UnityEngine;
[CreateAssetMenu]
public class Item : ScriptableObject
{
public string identifier;
public GameObject model;
public int value;
public double weight;
Item(string identifierStr)
{
this.identifier = identifierStr;
}
}
Here is my Inventory Class
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : List<Item>
{
public List<Item> itemList = new List<Item>();
// Default Constructor
Inventory()
{
}
public override string ToString()
{
string itemListString = " ";
for(int i = 0; i < itemList.Count; i++)
{
itemListString = itemListString += itemList[i].name;
}
return itemListString;
}
public void AddItem(Item itemToAdd)
{
for (int i = 0; i < itemList.Count + 1; i++)
{
if (itemList[i] == null)
{
itemList[i] = itemToAdd;
}
}
}
// Remove Item Not Finished
public void RemoveItem(Item itemToRemove)
{
for (int i = 0; i < itemList.Count; i++)
{
if (itemList[i] == itemToRemove)
{
itemList.Remove(itemToRemove);
}
}
}
}
The only solution that I can think of is to use ScriptableObject.CreateInstance to populate my game-world with the items that I wish to add to the inventory but even then I am unsure how to both: reference those Item Instances in code and, use a RayCast to determine that an Item Instance is being hit. Is it possible to create instances of a ScriptableObject in unity without code or will I have to populate my world through code?
Any help would be greatly appreciated I have been struggling with this for quite some time now.
So as with most things there are many ways to skin a cat. There are some brilliant and complicated inventory systems in the asset store that you can use if you feel like this is taking too much time. If it were me settings up this structure i would make an interface called something like "IInteractable" for you items that you want to the player to have the ability to pick up.That interface would look something like this
public interface IInteractable
{
//once a player has reference to this object they call this
//method to get an item back (which is your scriptable object)
Item GetItemInfo();
}
So in your OnCollision method instead of checking for a tag you could instead check against an interface like this.
IInteractable item = itemhit.collider.gameobject.GetComponent<IInteractable>();
if (item != null)
{
//Then you have found an item that needs to be picked up.
string name = item.GetItemInfo().identifier;
}
From here there are several ways you can go depending on what effects you want your inventory to have on your character. With some inventory you want items to have immediate effect while with others you want the effect only to be active while equipped.As far as spawning these items it would probably be easiest to just make prefabs with SO's attached and spawn those. Let me know if you have any other questions.

How can I create a dialogue system

using System.Collections;
using UnityEngine;
using TMPro;
public class NPCscript : Interactable {
public GameObject dialougeObj;
public TMP_Text dialouge;
public int Size;
public string dialougeSt;
public override void Interact () {
base.Interact ();
dialougeObj.SetActive (true);
NPCInteraction();
}
void OnTriggerExit(Collider other) {
dialougeObj.SetActive(false);
}
void NPCInteraction(){
dialouge.SetText(dialougeSt);
}
}
Please tell how can I have an array or list in which I can do use my size variable and loop it until all dialogues are said.
I tried but mine doesn't work.
HELP!
A Dialogue system in itself is a very broad topic. However, I don't think you will be able to pull it off with just a List of strings. If you are talking about giving the player choices to choose from then you will need to create a few custom classes.
First, you will need a class to define your Character's Options. This will be a very simple class. It will contain the string response and an integer that will represent a destination node.
public class DialogueOption {
public string Text;
public int DestinationNodeID;
}
Next, you will need a DialogueNode class. Consider this what the NPC is telling the character and then it also contains a list of, you guessed it, Dialogue Options. The other thing it will have is a NodeID. This is an integer that gives us a place to send us to from our Options. It will look a little like this:
public class DialogueNode {
public int NodeID = -1; //I use -1 as a way to exit a conversation.
//NodeId should be a positive number
public string Text;
public List<DialogueOption> Options;
}
Finally, you will need to create your Dialogue class, which is the simplest of them all, just a list of DialogueNodes and looks something like this:
public class Dialogue {
public List<DialogueNode> Nodes = new List<DialogueNode>();
}
You can have it where your character has just one dialogue script but, I did it differently where each character then had another class called DialogueList, which basically is a list of dialogues and it also contained the character's name that I could display and depending on the situation I could pick which dialogue I wanted my character to have with the player at the time.
public class DialogueList
{
public string name;
public List<Dialogue> dialogues = new List<Dialogue>();
}
This also has the added benefit of being easily converted to a Dictionary using the name as a key returning a list of dialogues if you wanted to set it up in that direction.
And somewhere else in your project you will need a DialogueManager class that will control everything. I typically make this a Singleton just so I can easily call it from anywhere. I won't show you all of it but I will show you the displaying part the rest is just setting the text and turning on and off objects.
public void RunDialogue(string name, Dialogue dia)
{
nameText.text = name;
StartCoroutine(run(dia));
}
IEnumerator run(Dialogue dia)
{
DialoguePanel.SetActive(true);
//start the convo
int node_id = 0;
//if the node is equal to -1 end the conversation
while (node_id != -1 )
{
//display the current node
DisplayNode(dia.Nodes[node_id]);
//reset the selected option
selected_option = -2;
//wait here until a selection is made by button click
while (selected_option == -2)
{
yield return new WaitForSeconds(0.25f);
}
//get the new id since it has changed
node_id = selected_option;
}
//the user exited the conversation
EndDialogue(node_id);
}
My buttons basically just had this simple method attached to them in their OnClick event that I set under the DisplayNode method. Basically, I take my buttons and give them this method and their parameter is whatever their DialogueOption.DestinationId is
public void SetSelectedOption(int x)
{
selected_option = x;
}
Hmm if you are asking for a Dialogue System it is very easy to create on Unity using UGUI.
1.) Create a canvas with a plane and a text I guess will do
*scale the plane ofcourse to your satisfaction and put it infront of the camera
*and about the text you must put it infront of the plane like the plane is on the back
*and the text is infront of the plane.
2.) Now setup your heirarchy like this
Canvas
Dialogue //Empty GameObject
Plane
Text
I'll make it pop out whenever you collides into an NPC something like that
[SerializeField] GameObject dialogueObject; //here drag the canvas
[SerializeField] GameObject text; //we will get its component for changing the text later on
void Start(){
text = GetComponent<Text>().text = "Sample Dialogue";
dialogueObject.setActive(false);
}
void OnCollisionEnter(collision collide){
if(collide.gameObject.name == "NPC"){
dialogue.setActive(true);
}
}
Simple as that . I hope it helps

How to create a custom inspector showing a list of lists in Unity?

well, I'm working on a specific part of a game in which I need to create a list of lists, a list of Game levels and in each list node a list of triggers (The level lists and each list inside it are variable in size).
This is all in the script LevelController.cs.
This is the Trigger class (it's simple like this for now)
[System.Serializable]
public class TriggerClass
{
public GameObject Trigger;
public bool Active;
public int Type;
public TriggerClass(GameObject obje, bool active = false, int type = 1)
{
Trigger = obje;
Active = active;
Type = type;
}
}
And a wrapper class to create the Levels List
[System.Serializable]
public class WrapperClass
{
public List<TriggerClass> Triggers = new List<TriggerClass>();
}
And of course
public List<WrapperClass> Levels = new List<WrapperClass>();
I serialized both and they show ok in standard Inspector draw in Unity, but I need to customize the Inspector showing this list, so I made a InspectorCustomizer.cs and a EditorList.cs in Editor folder.
For now I'm just trying to simulate the default draw that Unity does with the lists (an expand arrow, a size field that creates that amount of list nodes, and in each node the same thing, as I said, the default that happens if you uncomment the DrawDefaultInspector and comment the other 3 lines in InspectorCustomizer.cs)
The InspectorCustomizer:
[CustomEditor(typeof(LevelController))]
public class InspectorCustomizer : Editor
{
public override void OnInspectorGUI()
{
//DrawDefaultInspector();
serializedObject.Update();
EditorList.Show(serializedObject.FindProperty("Levels"));
serializedObject.ApplyModifiedProperties();
}
}
And here the EditorList.cs:
public static class EditorList
{
public static void Show (SerializedProperty list)
{
EditorGUILayout.PropertyField(list);
EditorGUI.indentLevel += 1;
if (list.isExpanded)
{
EditorGUILayout.PropertyField(list.FindPropertyRelative("Array.size"));
for (int i = 0; i < list.arraySize; i++)
{
EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i));
}
}
EditorGUI.indentLevel -= 1;
}
}
But this just shows the "Levels" list with empty elements, I click in the expande arrow in each element and nothing shows up, I know this happens because the script is iterating only through the Levels list and not each element within, but I don't know how to iterate through every element inside the Levels list to show the same thing.
I'm not really used to serialization so forgive me if this sounds easy.
I cannot do EditorList.Show(list[i]); inside the for loop because it cannot apply indexing to a serializedProperty of course, so how do I procced?
No, You don't need the EditorList.cs.
EditorGUILayout.PropertyField has a parameter includeChildren to show its children properties.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(LevelController))]
public class InspectorCustomizer : Editor
{
public override void OnInspectorGUI()
{
//DrawDefaultInspector();
serializedObject.Update();
EditorGUILayout.PropertyField(serializedObject.FindProperty("Levels"), true);
serializedObject.ApplyModifiedProperties();
}
}

Enum pointing to gameobjects?

So, I have this crazy idea to have enums pointing to gameobjects.
Here's what I want to do:
/* These enums would hold gameobjects instead of ints */
enum exampleEnum{
AddPanel,
ListPanel
}
public class GUIManager : MonoBehaviour {
void Start()
{
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
Is there any way to make this work? Or a workaround?
This might be possible with something other than an enum but I don't know of it if there is and I'm looking through the web for a such a solution.
This would satisfy your requirement, works for any amount of enum values or panels.
// Add this to each of your panels, the enum field can be integrated into your other behaviours as well
public class EnumPanel : MonoBehaviour
{
// configurable from the Editor, the unity way.
public ExampleEnum Type;
}
// Assign all your panles in the editor (or use FindObjectsByType<EnumPanel> in Start())
public class GUIManager : MonoBehaviour
{
// configurable from the Editor, the unity way.
public EnumPanel[] Panels;
void Start()
{
// Optionally find it at runtime, if assigning it via the editor is too much maintenance.
// Panels = FindObjectsByType<EnumPanel>();
EnablePanel(ExampleEnum.AddPanel);
}
void EnablePanel(ExampleEnum panelType)
{
foreach(var panel in Panels)
{
if(panel.Type == panelType)
EnablePanel(panel.gameObject);
}
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
I don't know why the answer from: #Paradox Forge was wrong but maybe this will help you.
System.Collections.Generic.Dictionary
I don't have a lot of time to explain the dictionary class but this is how you can use it.
This will cost some performance but has really nice readability
public class GUIManager : MonoBehaviour {
public enum exampleEnum{
AddPanel,
ListPanel
}
//For readability you can also add "using System.Collections.Generic;" on the top of your script
private System.Collections.Generic.Dictionary<exampleEnum,GameObject> exampleDictionary = new System.Collections.Generic.Dictionary<exampleEnum, GameObject>();
private GameObject SomeGameObject;
private GameObject SomeOtherGameObject;
void Start()
{
//You have to add all the enums and objects you want to use inside your GUIManager.
exampleDictionary.Add (exampleEnum.AddPanel, SomeGameObject); //Add panel will be linked to SomeGameObject
exampleDictionary.Add (exampleEnum.ListPanel, SomeOtherGameObject); //List Panel will be linked to SomeOtherGameObject
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(exampleEnum examplePanel)
{
if (!exampleDictionary.ContainsKey (examplePanel)) //If the given panel does not exist inside the dictionary
return; //Leave the method
GameObject panelToEnable = exampleDictionary [examplePanel]; //Will return the GameObject linked to given panel
panelToEnable.SetActive(true); //Enable the gameobject
}
}
If you want to know more about the Dictionary class go to: Dictionary

Categories

Resources