Serializable object is looking weird in the inspector - unity3d

So I have this small class called StoryTrigger:
namespace VisualNovelScripting
{
[System.Serializable]
public class StoryTrigger
{
[SerializeField] public string triggerName;
public bool value;
public StoryTrigger(string name, bool value = false)
{
this.triggerName = name;
this.value = value;
}
}
}
And I want to make a ScriptableObject with a list of StoryTrigger objects that could add, remove and edit elements right on the unity inspector.
I assumed that if the StoryTrigger had the [System.Serializable] modified it should work. And It kind of works but looks like this on the inspector:
I want to know if there's something I am doing wrong or something I could do better so it doesn't look this weird on the inspector or if this is an issue on the unity editor.

I saw that when I add only 1 element to the StoryTriggers list it does look weird on the inspector like shown in the question.
But when I add multiple elements it stops looking weird. So I can work with it.
I assume this is a bug on the Unity Editor since the condition to the weird behavior is on the amount of elements on the list.

Related

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;
}
}

Unity serialized fields migration

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

Unity 2D: Simple Cutscene animation system/manager

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.

How can I set a public variable using getChildIndex in as3?

I was coding a very simple program that lets you move around a circle, with also a rectangle in the stage. I wanted to make the circle get in front of the rectangle while you are dragging it, but when you released the mouse, the circle would be sent back.
I don't know how to set a public variable using the getChildIndex method. I don't really care about the rest of the code. I'm mainly interested in how can I make the getChildIndex method work with a public variable.
package code
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.Sprite;
public class Main extends MovieClip
{
public var myCircleIndex:int = getChildIndex(myCircle);
public function Main()
{
myCircle.addEventListener(MouseEvent.MOUSE_DOWN, mouseClicking);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
}
public function mouseClicking(e:MouseEvent): void
{
myCircle.startDrag();
setChildIndex(myCircle, numChildren-1);
}
public function mouseReleased(e:MouseEvent): void
{
myCircle.stopDrag();
setChildIndex(myCircle, myCircleIndex);
}
}
}
I'm using an instance ("myCircle") that I created directly in the stage as a movie clip.
The problem is in the public var I set at the beginning, it doesn't let me get the child index of myCircle, but if I put the same line inside a function, it works.
I know I could directly put the index number of myCircle in the last line (and erasing the public var myCircleIndex), but I figured out that there would be a way of using the getChildIndex for a public var in a class.
How do you use getChildIndex in a public variable inside a class?
The reason it doesn't work, is because your timeline objects don't yet exist when the line public var myCircleIndex:int runs.
You shouldn't try and access non-primitive objects in your class level variable declarations for this very reason, as nothing else in the class is available yet when those vars are created.
Here is how you can refactor this (see the code comments):
public class Main extends MovieClip
{
public var myCircleIndex:int; //just create the reference here, don't assign it
public var myCircle:flash.display.DisplayObject; //this line is just for better compile time checking and code completion, completely optional
public function Main()
{
//wait for all the display stuff to be created before trying to access it. The constructor function can run before timeline stuff is created, so it's not safe to reference stage or timeline objects here.
if(!stage){
this.addEventListener(Event.ADDED_TO_STAGE, timelineCreated);
}else {
timelineCreated(null);
}
}
private function timelineCreated(e:Event):void {
//now that we're certain the timeline stuff has been created, we can reference timeline objects and stage:
//store the initial z-index of myCircle
myCircleIndex = getChildIndex(myCircle);
//the rest of your code that was in the construction -Main()- before
myCircle.addEventListener(MouseEvent.MOUSE_DOWN, mouseClicking);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
}
//no change to any of the following stuff
public function mouseClicking(e:MouseEvent): void
{
myCircle.startDrag();
setChildIndex(myCircle, numChildren-1);
}
public function mouseReleased(e:MouseEvent): void
{
myCircle.stopDrag();
setChildIndex(myCircle, myCircleIndex);
}
}
All you need to do to put the circle behind the square is on release do addChild(myRectangle) or addChildAt(myCircle, 0);
You are overcomplicating things by trying to track a variable in my opinion. Let flash sort it out behind the scenes.
If you want a little more finesse and want to just put the circle directly behind the square (if there were 100 layers and the square is at level 12, but you aren't sure which level the square is at) you could do
addChildAt(myCircle, getChildIndex(myRectangle)-1);
note
setChildIndex(myCircle, numChildren-1);
That's fine to do it that way. The more common way to do this is just
addChild(myCircle);
It does the exact same thing. Many people are confused by this thinking this would add a new myCircle but it just brings it to the front if it's already in the display list, and if it's not in the display list, it adds it to the display list at the top z-order (numChildren-1).