Unity serialized fields migration - unity3d

I have a big amount of prefabs. Those prefabs have several instances of the same script, which contains the following fields:
[SerializeField]
private AudioClip[] _audioClip;
[SerializeField, Range(0, 1)]
private float _volume = 1;
Since I would like to be able to control the volume of each audio clip separately, I would like to use:
[SerializeField]
private VolumedAudioClip[] _audioClips;
Where:
[Serializable]
public class VolumedAudioClip
{
[SerializeField]
public AudioClip _audioClip;
[SerializeField, Range(0, 1)]
public float _volume = 1;
}
Problem is, that if I change it now, all of the prefabs will lose the references to the audio clips already set.
I know of FormerlySerializedAs attribute, it doesn't help in my case (only if you rename a field).
My current direction is to write an editor script that will read from the old fields and put the data in the new fields.
Would be happy to hear any better suggestions...

If you have the flexibility to use a List<VolumedAudioClip> instead of the VolumedAudioClip[] I have a very simple and fast solution for you:
using System.Collections.Generic;
using System;
using UnityEngine;
public class test : MonoBehaviour
{
[SerializeField]
private AudioClip[] _audioClip;
[SerializeField, Range(0, 1)]
private float _volume = 1;
[Serializable]
public class VolumedAudioClip
{
[SerializeField]
public AudioClip_audioClip;
[SerializeField, Range(0, 1)]
public float _volume = 1;
public VolumedAudioClip(AudioClip clip)
{
_audioClip = clip;
}
}
[SerializeField]
private List<VolumedAudioClip> _audioClips;
//THIS ADDS A CONTEXT MENU BUTTON TO THE INSPECTOR!
[ContextMenu("Copy the array")]
private void CopyIt()
{
//To make sure we don't add things multiple times
_audioClips.Clear();
//Than just insert the items from the original array
foreach (AudioClip clip in _audioClip)
{
VolumedAudioClip newClip = new VolumedAudioClip(clip);
_audioClips.Add(newClip);
}
}
}
The [ContextMenu] attribute adds the CopyIt() method to the Context-Menu of the Inspector. Here is how you use it than: (I used int[] _audioClip for demonstration)
So at the beginning we have the original array of AudioClip[] _audioClip filled and the List of List<VolumedAudioClip> _audioClips empty.
Now if you go to the context menu (gear icon) in the Inspector, our ContextMenu item "Copy the array" shows up. (isn't this awesom!)
After a simple click you see the elements of AudioClip[] _audioClip are copied to the new List List<VolumedAudioClip> _audioClips
NOTE
I wrote this really quick and it is sure not perfect so if you make already tweaks on the volumes and than use this function again, the volume tweaks will be lost since the List is filled again with the default value.
So I would recoment to use the function and than remove it from your script so you can't screw up by accident.
Hope it helps you

Related

I'm getting an UnassignedReferenceException when the reference is set. I've seen other questions similar but not exactly the same

As the title says i'm getting the UnassignedReferenceException error for a variable already set. Using ScriptableObjects im working on an inventory system(partially from scratch) and im trying to access the EquipmentUi in another class using a GameObject to hold the prefab containing the Character script. There is no issue with this working to access the Character script as shown in the picture because i can access the name. However, when i try and access the EquipmentUI of that character it gives the error. This isnt the last part i need access to but i have figured out that the UI is the part i cant access, i need the script held in it(which has worked before in another class).
The variable is already assigned, there is no other object in my scene with the same script attached, and the code can access other parts of the script i want access to which is why i made a new post after seeing the other posts and not seeing one that had quite the same issue.
using System.Collections;
using System.Collections.Generic;
using UnitEngine;
[CreateAssetMenu(fileName = "New Character Equipment", menuName = "Inventory/Character/CharacterEquipment)]
public class CharacterEquipmentObject : ScriptableObject
{
[SerializeField]
protected Player user;
[SerializeField]
private GameObject equipmentUser; // Only used to get the user because SOs cant getObject<>
[SerializeField]
public EquipmentObject[] equipment = new EquipmentObject[8];
[SerializeField]
EquipmentDisplay equipmentUI;
//private string[] SlotList = new string[8]{"Helmet", "Shoulders", "Chest", "MainHand", "OffHand", "Ring", "Legs", "Feet"}; may need later
public void start() // must be called in display
{
user = equipmentUser.GetComponent<Player>(); // set the user to be used in other classes\
Debug.Log("user in CEO: " + user.characterName); // this displays fine
Debug.Log("user " + user.characterName + "'s equipment: " + user.EquipmentUI.name); // This is what the editor says is empty when the slot has it assigned
equipmentUI = user.EquipmentUI.GetComponentInChildren<EquipmentDisplay>();
Debug.Log("equipmentUI: " + equipmentUI.name);
}
Thanks for any help in advance.
Player class:
public class Player : Character
{
[SerializeField]
protected Character[] companions = new Character[3];
[SerializeField]
GameObject InventoryUI;
public GameObject EquipmentUI;
bool isSelling = false;
public string characterName;
protected override void Start()
{
InventoryUI.SetActive(false);
EquipmentUI.SetActive(false);
Debug.Log("Player Equipment UI: " + EquipmentUI.GetComponentInChildren<EquipmentDisplay>().name);
base.Start();
}
private void OnTriggerEnter(Collider other)
{
var item = other.GetComponent<InteractItem>();
if(item)
{
characterInventory.addItem(item._item, 1);
useItem(item._item);
}
Destroy(other.gameObject);
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.I))
{
InventoryUI.SetActive(!InventoryUI.activeSelf);
}
if(Input.GetKeyDown(KeyCode.C))
{
EquipmentUI.SetActive(!EquipmentUI.activeSelf);
if(EquipmentUI.activeSelf == true)
{
EquipmentUI.GetComponentInChildren<EquipmentDisplay>().updateEquipmentSlots();
}
}
}
Thanks for all the help on here but i figured out it is a ScriptableObjects issue as i went in and changed the way it is run, even putting the function to edit the users InventoryObject into the player itself it still gave the same issue.
In the player function Update() that does not have the InventoryObject(ScriptableObject) the update runs fine but as soon as the player function EmptySlot() is called from the InventoryObject it sends the same error.
Not entirely sure why this is but SOs are unable to access the GameObject that holds the script for the inventory display in them whatsoever. If anyone has a reason as to why this is I would love to know, but i am going to move on from this particular part and change how it works.
You might be experiencing a bug I have been facing where the element variable info does not update to show variable changes.
To force it to update, cause an error in your code. Switch to unity, when it shows you the error go back and fix it, and see if the variable value changes then.

Unity - how to transfer a set sprite value to different scenes? [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 1 year ago.
I'm new to working in unity and i don't quite understand how to transfer data between scenes. In my case it's the sprites for my inventory when the player picks up an item. here is the code for the items sprites etc.
public class Items : MonoBehaviour
{
public string ItemName;
public string ItemText;
public int itemInList;
public Sprite itemSprite;
public InventorySlot inventorySlot;
public ItemSprites itemSprites;
public Sprite blueberrySprite;
public Sprite cheesecakeSprite;
public Sprite whitebreadSprite;
public Sprite cookiesSprite;
public void Awake()
{
//DontDestroyOnLoad(this);
}
public void gettingID()
{
GameObject Go = new GameObject();
Go.AddComponent<InventorySlot>();
inventorySlot = Go.GetComponent<InventorySlot>();
inventorySlot.itemID = itemInList;
}
public void ItemList()
{
// Cheesecake
if(itemInList == 1)
{
ItemName = "Cheesecake";
ItemText = "A delishious Juicy Cheesecake";
itemSprite = cheesecakeSprite;
}
// White Bread
if (itemInList == 2)
{
ItemName = "White Bread";
ItemText = "a basic white loaf that smells amazing";
itemSprite = whitebreadSprite;
}
// Cookies
if (itemInList == 3)
{
ItemName = "Cookies";
ItemText = "just plain Chocolatechip cookies, still delishious though";
itemSprite = cookiesSprite;
}
// Blueberries
if (itemInList == 4)
{
ItemName = "Blueberries";
ItemText = "Big juicy yummy blueberries!";
itemSprite = blueberrySprite;
Debug.Log(ItemText);
}
}
it works just fine when I'm in the scene where the script exist but when I try to use it in my new scene the image will turn out white(blank). I guess that is because the gameobject is not set anymore in the new scene, but how do i transfer the set gameobject sprite to a new scene? I now that you can make stuff static but if I read correctly then it doesn't work on gameobjects. and Dontdestroyonload doen't seem to work either cus the script is not attached to a gameobject in the scene.
a) You can make your sprite a static property in your class and you should be able to access it from anywhere in unity. You can make a simple DataHolder class to hold this value and other static values in your Game (It need not be a MonoBehaviour). However, you will need to load them somehow (From resources or AssetBundles) into your static Array
b) Check my Answer here on how to properly persist a GameObject in the Game if you are using DontDestroyOnLoad and have it attached to a GameObject in the scene. This should be a good option to use, considering you already have a MonoBehaviour class. Just attach it to a GameObject and Follow the answer in the link to maintain one correct instance of the Component.
c) Consider using a simple ScriptableObject if you want to save these values across multiple Game Sessions. It is a simple data container that helps you save data Independent of Classes.
Follow this link for Unity's Documentation on ScriptableObjects

I'm trying to figure out how to change from one static variable to another in the inspector

new game dev here. I'm not sure if this is a stupid question but I will ask it anyway. I'm trying to figure out how to change from one variable to another in the inspector. I have a few static variables in an empty game object called currencyMaster. Sorry if my question is hard to understand.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class currencyDisplay : MonoBehaviour
{
private TextMeshProUGUI textMecH;
void Start()
{
textMecH = GetComponent<TextMeshProUGUI>();
}
// Update is called once per frame
void Update()
{
//i want to change moneyPlus since all the variables are in
//currencyMaster
textMecH.text = currencyMaster.moneyPlus.ToString("0.0");
}
}
By default, Unity only serializes public fields. In order to expose a private variable to the inspector, you need to mark it with the attribute SerializeField.
[SerializeField] private TextMeshProUGUI textMecH;

Saving string variables in a custom EditorWindow while formatted like a TextArea

Basically, I want to figure out how I can:
Save string (or any) variables in a custom editor window (inheriting
from EditorWindow) when they are changed in that window.
Display strings in a format like a TextArea while still allowing
saving changes as mentioned above.
Display strings from a string array by index, rather than individually defined strings (I've had trouble with this before)
If you know how to do the above in a custom inspector too
(inheriting from Editor, not EditorWindow), that'd be great too.
I've run into this issue a few times with different classes inheriting from Editor, and previously solved by using a PropertyField rather than a TextArea/TextField, but that gets rid of the TextArea-style formatting that I want.
Also, classes inheriting from EditorWindow don't seem to allow it in the same way (t = (script type)target; doesn't work, and PropertyField needs it)..?
I'm pretty new to custom inspectors and this stuff, so code examples would be super helpful if possible.
Thanks!
Before starting a general note because you mentioned it in your question:
Whenever possible I strongly recommend to avoid using target at all! In particular do not set any fields directly. this makes things like marking your scene direty and thus saving changes and also Undo/Redo functionality pretty complicated as you will have to implement it by yourself!
Rather always go through SerializedProperty combined with SerializedObject.Update and SerializedObject.ApplyModifiedProperties (examples will be below). This handles all this stuff like marking dirty and thus saving the scene changes and Undo/Redo automatically for you!
Then to the TextArea.
Lets say you have a class like
public class Example : MonoBehaviour
{
[SerializeField] private string _exampleString;
public string AnotherExampleString;
}
Basically there are three main options. I will do the Editor (custom Inspector) script first since there you are a bit more flexible.
The EditorWindow will be below.
Editor Attribute [TextArea]
Actually you wouldn't even need an Editor script at all! Simply tag the according field(s) as [TextArea] like this:
public class Example : MonoBehaviour
{
[SerializeField] [TextArea] private string _exampleString;
// You can also directly configure the min and max line count here as well
// By default it is 3 lines
[TextAre(3,7)] public string AnotherExampleString;
}
This already looks like this
EditorGUILayout.PropertyField
Then if you still need the Editor script the good thing about a EditorGUILayout.PropertyField is it automatically uses the correct drawer for the according type ... and it also applies all editor attributes! Isn't this great?
So simply having and Editor like
[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
private SerializedProperty _exampleString;
private SerializedProperty AnotherExampleString;
private void OnEnable()
{
// Link in the serialized properties to their according fields
_exampleString = serializedObject.FindProperty("_exampleString");
AnotherExampleString = serializedObject.FindProperty("AnotherExampleString");
}
public override void OnInspectorGUI()
{
DrawScriptField();
// load the real target values into the serialized properties
serializedObject.Update();
EditorGUILayout.PropertyField(_exampleString);
EditorGUILayout.PropertyField(AnotherExampleString);
// write back the changed properties into the real target
serializedObject.ApplyModifiedProperties();
}
// Little bonus from my side so you have the script field on top
private void DrawScriptField()
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((Example)target), typeof(Example), false);
EditorGUILayout.Space();
EditorGUI.EndDisabledGroup();
}
}
The result looks basically exactly the same:
EditorGUILayout.TextField
Using a EditorGUILayout.TextArea you can display any string as a multi-line text area. This also applies to an EditorWindow.
Lets say again we didn't tag our string fields
public class Example : MonoBehaviour
{
[SerializeField] private string _exampleString;
public string AnotherExampleString;
}
But we can make them appear just as before using this Editor script:
[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
private SerializedProperty _exampleString;
private SerializedProperty AnotherExampleString;
private Vector2 scroll1;
private Vector2 scroll2;
private void OnEnable()
{
// Link in the serialized properties to their according fields
_exampleString = serializedObject.FindProperty("_exampleString");
AnotherExampleString = serializedObject.FindProperty("AnotherExampleString");
}
public override void OnInspectorGUI()
{
DrawScriptField();
// load the real target values into the serialized properties
serializedObject.Update();
EditorGUILayout.PrefixLabel(_exampleString.displayName);
scroll1 = EditorGUILayout.BeginScrollView(scroll1,GUILayout.MaxHeight(3 * EditorGUIUtility.singleLineHeight));
_exampleString.stringValue = EditorGUILayout.TextArea(_exampleString.stringValue, EditorStyles.textArea);
EditorGUILayout.EndScrollView();
EditorGUILayout.PrefixLabel(AnotherExampleString.displayName);
scroll2 = EditorGUILayout.BeginScrollView(scroll2, GUILayout.MaxHeight(7 * EditorGUIUtility.singleLineHeight));
AnotherExampleString.stringValue = EditorGUILayout.TextArea(AnotherExampleString.stringValue);
EditorGUILayout.EndScrollView();
// write back the changed properties into the real target
serializedObject.ApplyModifiedProperties();
}
// Little bonus from my side so you have the script field on top
private void DrawScriptField()
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((Example)target), typeof(Example), false);
EditorGUILayout.Space();
EditorGUI.EndDisabledGroup();
}
}
Though you can see we already had to fake it a bit using the additional EditorGUILayout.BeginScrollView
This same thing you can also do in an EditorWindow. Most of the times it makes not much sense to go through SerializedProperty for EditorWindow
public class ExampleWindow : EditorWindow
{
private string exampleString;
private Vector2 scroll;
[MenuItem("Example/Show ExampleWindow")]
private static void Initialize()
{
var window = GetWindow<ExampleWindow>();
window.Show();
}
private void OnGUI()
{
EditorGUILayout.PrefixLabel("Example String");
scroll = EditorGUILayout.BeginScrollView(scroll,GUILayout.MaxHeight(3 * EditorGUIUtility.singleLineHeight));
exampleString = EditorGUILayout.TextArea(exampleString, EditorStyles.textArea);
EditorGUILayout.EndScrollView();
}
}
which results in

Auto-Draggable Variables

I want to be able to drag & drop variables (such as floats, integers, maybe even classes) in the Inspector view. So that if I have FirstScript and SecondScript I could easily drag & drop variables between them so that they can change each others' data. I need this to create more modular workflow.
I know that ScriptableObjects allow similar functionality but I need to create them manually through Create button in Inspector. Instead I want this to happen automatically.
This should look something like scripts below. So that I could drag the original_float to float_to_change field right in the Inspector.
// First Component
public class FirstScript : MonoBehaviour
{
public DraggableFloat original_float = 5.0f;
}
// SecondComponent
public class SecondScript: MonoBehaviour
{
public DraggableFloat float_to_change;
public void ChangeFloat()
{
// The value of original_float is changed through reference
float_to_change += 10.0f;
}
}