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;
}
}
Related
I am creating a digital boardgame, which consits of muliple boards, between which the gamepieces are moved by the players.
The script of the game board needs a 2D array of positions to know where to move a gamepiece when it recives one.
Currently to mark the positions on the game boards, I added placeholder gameobjects to the prefab and named them "spawnpoint\d".In the Awake() method I use Transform.Find() to search for those gameobjects. Then, after I save their positions I call Destroy() on them, so they do not show up in the game.
I see two problems:
This is done for all Instantiated game board, altough the positions are the same on all of them.
I read that using Transform.Find() is heavily discourged by the experts in the community.
I wish to store the spawnpoint positions in a static array, so all instances refer to the same data. Furthermore I wish to easily modify these positions in the editor with visual help.
I tried serializing static members, but those do not show up in the editor to be able to modify.
[SerializeField]
public static int TestNumber;
TLDR:
How to make static members visually changeable from the Unity editor?
tl;dr you can't, static fields are not serialized.
You can do e.g.
[SerializeField] private Transform[] spawnPoints;
public static Transform[] SpawnPoints;
private void Awake ()
{
SpawnPoints = spawnPoints;
}
In general I would suggest rather using something like this:
// Simply attach this class to each GameObject that shall be a spawn point
// MAKE SURE IT IS ACTIVE AND ENABLED BY DEFAULT
public class SpawnPoint : MonoBehaviour
{
// Each SpawnPoint (un)registers itself here
private static readonly HasSet<SpawnPoint> _instances = new HashSet<SpawnPoint>();
// For the public return a new HashSet to make sure nobody can modify the
// original _instances from the outside
public static HashSet<SpawnPoint>() Instances => new HashSet<SpawnPoint>(_instancea);
private void Awake()
{
// Register yourself to the existing instances
_instances.Add(this);
// Optional: make sure this object is not destroyed when a new scene is loaded
DontDestroyOnLoad (gameObject);
// simply hide the entire gameObject
gameObject.SetActive(false);
}
private void Destroy ()
{
// Unregister yourself from the instances
_instances.Remove(this);
}
}
This way
each spawn point Auto-Registers itself to the Instances so you don't even need to serialize this via the Inspector -> you also can't forget any
the spawn points don't get Destroyed when a new scene is loaded (if you use the DontDestroyOnLoad - otherwise they are destroyed and auto-removed from the instances)
you disable the objects (though actually if they have nothing attached except this script it wouldn't matter anyway)
you can easily access all the spawn points without using expensive stuff like Find or FindObjectsOfType but rather simply via the property
var availableSpawnPoints = SpawnPoint.Instances;
Ok, so, a Slider in Unity has an "On Value Changed (Single)" property. As far as I've managed so far, you can set on it a callback function that can take a parameter (e.g. a string) which you hardcode in the Inspector, and/or a function that takes a float (the new value that the slider has been set to). However, I'd like to be able to pass both, and I haven't seen a way to do it. For example, I'd like to have a function saveValue(string name, float value), and several sliders feeding their values into it - each slider would have a different name written in the Inspector, and the updated value would be passed as value. I suspect Unity does not support that, but I haven't found any documentation explicitly describing this feature, so I'm not sure. Can this be done?
This should get you started:
Create a class that all of your sliders can access (for example by reference). And put the method you want them to call in there. The method should take the two parameters that you want to use (in my case I want it to take a GameObject and a float):
public class SliderManagerScript : MonoBehaviour
{
public void SliderValueChangeHandler(GameObject slider, float value)
{
Debug.Log("Slider name: " + slider.gameObject.transform.name);
Debug.Log("Slider value: " + value);
}
}
On each of the sliders that you want to call the above method, add a script that can access the above class and add an event listener for the Slider's onValueChanged property. Register a delegate for that listener which calls the method shown above and passes in the appropriate values. E.g
public class SliderScript : MonoBehaviour
{
public GameObject sliderManager;
void Start()
{
GetComponent<Slider>().onValueChanged.AddListener(delegate { sliderManager.GetComponent<SliderManagerScript>().SliderValueChangeHandler(this.gameObject, this.GetComponent<Slider>().value); });
}
}
And that should work fine. In fact, you should be able to simplify it a lot more depending on how you've architected your code.
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
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
I am struggeling to come up with a smart way to deal with cutscenes in my 2D game. I think I have decided to Create/Control the cutscenes using the Animator, and moving the actual GameObjects around.
What I am struggeling with is:
This approach means each GameObject needs it's own Animator. So how do I then trigger the correct Cutscene? I am going to use game stages to control the game and its progress, so for example:
if(gameStage == 34 && entering scene castle)
{
playCorrectCutscene();
}
How and where do I keep references to all my cutscenes? Say I have 22 NPCs in a scene, and 15 of those have their own Animator since they have a cutscene, how do I play the Animation from NPC_11?
I guess what I am looking for is some sort of "Cutscene Manager". The best possible solution would be to have a List of every cutscene (animation) with an ID. Then I could just use the ID to play the correct animation. But since each Animation is stored in the actual GameObject I have no idea if that is possible.
Hope Im making sense.
Once you create complete prefab with 1 Animator per prefab, then you can use bellow method to create instances on the fly:
Let say you have CutsceneManager script like this:
class CutsceneManager : MonoBehaviour {
public GameObject InstantiateResource(string folder, string name)
{
GameObject resource = Instantiate<GameObject>(Resources.Load<GameObject>(folder + "/" + name));
if (resource == null)
{
Debug.LogErrorFormat("Cannot find resource {0} in {1}", name, folder);
return null;
}
resource.name = name;
return resource;
}
}
The easiest way to use it is to attach it to Empty object. Then you can create public variable for manager in other scripts, where you would need to show cutscene, like this:
class SomeOtherScript : MonoBehaviour {
public CutsceneManager CutsceneManager;
}
This will let you drag&drop CutsceneManager empty into each script where you need it. This way you have 1 instance of cutscenemanager for all classes.
Now, in place where you would need to play custscene, you instantiate:
class SomeOtherScript : MonoBehaviour {
public CutsceneManager CutsceneManager;
public void PlayMyCutscene() {
GameObject cutscene = CutsceneManager.InstantiateResource("Cutscenes", "SomeOtherCutscene");
cutscene.setActive(true); // Or whetever other method to fire it off
}
}
On folder structure you would need to have: Assets\Resources\Cutscenes
Cutscenes would be called: SomeOtherCutscene.prefab
Notice there is no need to include .prefab when you are instantiating one - Unity framework will "know" and add it for you.