Can i make a dropdown in the component of a script? - Unity3D - c#

Im making an script, and i want to make a dropdown that if is an option, show some variables.
Like in RigidBody 2D:
Body Type: if is in "Dynamic" show an amount of variables to set, but if is in "Kinematic" show other amount of variables to set.
Any ideas to do it?
I answer any question.
I tried to found in Google, Youtube but i didn't found anything about it.

You can either write a custom inspector or use an enum
public enum Shape
{
Circle,
Square,
Triangle
}
and add a field into a MonoBehaviour
public Shape shape;
Will look like that
And the hard and long way
EditorGUILayout.Popup(indexOfTheSelectedOption, stringArrayOfOptions);
Here's an example
[CustomEditor(typeof(MyScript))]
public class MyScriptEditor : Editor
{
// the index of selected option
private int index;
public override void OnInspectorGUI()
{
// the index of selected option
index = EditorGUILayout.Popup(index, new string[] { "Option1", "etc" });
}
}
You can find more here and here.
Edited:
As you asked in the comments
[CustomEditor(typeof(MyScript))]
public class MyScriptEditor : Editor
{
// the serialized fields of the varibles you wanna change
SerialiedProperty var1, var2, var3; // ...etc.
// the index of selected option
private int index;
private void Awake()
{
var1 = serializedObject.FindProperty("var1");
var2 = serializedObject.FindProperty("var2");
var3 = serializedObject.FindProperty("var2"); // the string is the name of your field
}
public override void OnInspectorGUI()
{
// the index of selected option
index = EditorGUILayout.Popup(index, new string[] { "Option1", "etc" });
object yourVal;
switch (index)
{
case 0:
yourVal = EditorGUILayout.FloatField(yourVal);
break;
/*
and etc,
you create the field in inspector you want for eevery index
but tbh, that's a bad idea to do so
*/
}
serializedObject.ApplyModifiedProperties();
}
}

Related

How to pass the current clicked button from a list of buttons in Unity? [duplicate]

This question already has answers here:
Impossible value in lambda expression
(2 answers)
Closed 19 days ago.
I have a list of Buttons that get activated after certain conditions are met. I then add a listener to each button to call a function when clicked. That function then needs to be able to get the text off the button that was clicked. This is what I've tried but it returns an out of range error, I think because it calls the function once clicked, and at that point the variable i is maxed out.
public List<GameObject> ButtonList = new List<GameObject>();
void Start() {
for (int i = 0; i < ButtonList.Count; i++) {
ButtonList[i].SetActive(true);
ButtonList[i].GetComponent<Button>().onClick.AddListener(() => {GetButtonText(ButtonList[i]); });
}
}
public void GetButtonText(GameObject SelectedButton) {
Debug.Log(SelectedButton.GetComponentInChildren<TMP_Text>().text)
}
I thought about creating game objects for each button and assigning them in the inspector, but the number of buttons in the list is likely to fluctuate, so it needs to remain dynamic. Additionally, because the game object this script is attached to is a prefab, I can't set up the listeners in the inspector either.
First of all I would suggest to make it list of Button and not a GameObjects you still will be able to access GameObject without call GetComponent each time.
This think happened because it use the last value of i:
you need to redo it like that:
public List<GameObject> ButtonList = new List<GameObject>();
void Start() {
for (int i = 0; i < ButtonList.Count; i++) {
var button = ButtonList[i];
button.SetActive(true);
button.GetComponent<Button>().onClick.AddListener(() => {GetButtonText(button); });
}
}
public void GetButtonText(GameObject SelectedButton) {
Debug.Log(SelectedButton.GetComponentInChildren<TMP_Text>().text)
}
Base And I rater suggest to create a class to wrap the buttons
public class MyButton : MonoBehaviour
{
public event Action<int> OnClick;
[SerializeField] private Button button;
[SerializeField] private Text text;
private int id = -1;
private void Awake()
{
button.onClick.AddListener(Click);
}
private void Click()
{
OnClick?.Invoke(id);
}
public void Init(int index)
{
id = index;
}
public void SetText(string message)
{
text.text = message;
}
}

Selection and automatic deselection problem

as I stated above, I need help with my c# code which I'm using in unity to make a 3D game.
When I click on an object it should change color and if there was previously another object selected, I need it to be deselected automatically.
I already wrote a code which is supposed to at least help me select(change a material color) but it isn't working.
I am checking if the left mouse button is pressed down and using raycast to check if there is something under a cursor. Also wrote a Selection class with 2 methods(Select/deselect) that are supposed to change color of an object.
I'll be thankful if you can help me.
Here's the code with mouse click:
public class ClickSelect : MonoBehaviour
{
private void Update()
{
Click();
}
public void Click()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
Selectable selectable = hit.collider.gameObject.GetComponent<Selectable>();
if (selectable)
{
hit.collider.gameObject.GetComponent<Selectable>().Select();
}
}
}
}
}
And this is code of a selection class:
public class Selectable : MonoBehaviour
{
public void Select()
{
GetComponent<Renderer>().material.color = Color.yellow;
}
public void Deselect()
{
GetComponent<Renderer>().material.color = Color.gray;
}
}
First you should check if there is a Collider on the Selectable. If not add one. Because you are asking for a Ray that hits a collider and if there's no Collider it will return always null
then this
Selectable selectable = hit.collider.gameObject.GetComponent<Selectable>();
if (selectable)
{
hit.collider.gameObject.GetComponent<Selectable>().Select();
}
is complete Nonsense
why you get the Selectable in First Place and then the Selectable again?
And you don't need a Collider on it to detect the Transform so you can remove the hit.collider to hit.gameObject or better is hit.transform (Because u don't need the whole GameObject)
hit.collider.gameObject.TryGetComponent(out Selectable selectable);
//hit.transform.TryGetComponent(out Selectable selectable)
if(selectable != null)
{
selectable.Select();
}
Then instead of
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Try
Ray ray = Camera.main.ViewportPointToRay(Input.mousePosition);
hope it helps you, if so, feel free to mark it as solved and correct Answer.
To expand a bit on this answer, once you get the raycast itself to work properly, you want to store the current selection so you can fire deselect where necessary.
I would keep this information in the Selectable class itself like e.g.
public class Selectable : MonoBehaviour
{
// Stores the current selection and can only be modified by this class
private static Selectable _currentSelection;
// Public read-only accessor if needed
public static Selectable CurrentSelection => _currentSelection;
// Deselcts if current selection and resets the selection
public static void ClearSelection()
{
// if there is a current selection -> deselect it
if(_currentSelection) _currentSelection.Deselect();
// "forget" the reference
_currentSelection = null;
}
// In general rather cache reused references
[SerializeField] private Renderer _renderer;
// Optional accessor
public bool IsSelected => _currentSelection == this;
private void Awake ()
{
// if not referenced yet get it ONCE on runtime
if(!_renderer) _renderer = GetComponent<Renderer>();
// I would also ensure the default state initially
Deselect();
}
public void Select()
{
// if this is already the selected object -> nothing to do
if(_currentSelection == this) return;
// otherwise first clear any existing selection
ClearSelection();
// set your color
_renderer.material.color = Color.yellow;
// and store yourself as the current selection
_currentSelection = this;
}
public void Deselect()
{
// set your color
_renderer.material.color = Color.gray;
// if this is the current selection forget the reference
// usually this should always be the case anyway
if(_currentSelection == this)
{
_currentSelection = null;
}
}
}
and then
public class ClickSelect : MonoBehaviour
{
// in general Camera.main is expensive -> rather store he reference and reuse it
// Already reference this via the Inspector if possible
[SerializeField] private Camera _mainCamera;
private void Awake ()
{
// as fallback get it ONCE on runtime
if(!_mainCamera) _mainCamera = Camera.main;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
var ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out var hit))
{
// Is what you clicked on a selectable?
if (hit.transform.TryGetComponent<Selectable>(out var selectable))
{
// Internally already handles deselection of current selected and skips if already selected this
selectable.Select();
}
// Optional: Deselect the current selection if you click on something else
else
{
Selectable.ClearSelection();
}
}
// Optional: Deselect the current selection if you click on nothing
else
{
Selectable.ClearSelection();
}
}
}
}

Simple 2d program working with colors, buttons, and if statements

There are 3 buttons present. Clicking a button will change its color. The task here is to match every color of the buttons to a shade of green. Now, I have the buttons programmed to cycle through 4 colors, the same palette. It works, of course.
What I want to implement now is, to make a text appear once all the buttons match colors. I'm working on Unity and the c# script is on Visual Studio.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ChangeColor : MonoBehaviour
{
[SerializeField] int count = 0;
[SerializeField] Color[] colorArray;
public void button ()
{
if(count < colorArray.Length)
{
gameObject.GetComponent<Image>().color = new Color(colorArray[count].r, colorArray[count].g, colorArray[count].b);
if(count == colorArray.Length - 1)
{
count = -1;
}
count += 1;
}
}
}
First of all your button method can be simplified a lot.
And then I would use a callback that checks your colors everytime one of the buttons was pressed:
public class ChangeColor : MonoBehaviour
{
[SerializeField] int count = 0;
[SerializeField] Color[] colorArray;
[SerializeField] private Image _image;
public Color CurrentColor => _image.color;
public event Action OnChangedColor;
private void Awake ()
{
if(!_image) _image = GetComponent<Image>();
}
public void button ()
{
var count = (count + 1) % colorArray.Length;
var nextColor = colorArray[Count];
_image.color = nextColor;
OnChangedColor?.Invoke();
}
}
And now in a central controller script you have references to all your buttons ChangeColor components and their according target Color like e.g.
[Serializable]
public class ButtonColorPair
{
public ChangeColor changeColor;
public Color targetColor;
// Returns true if a ChangeColor is referenced and the colors are matching
public bool IsMatching => changeColor && changeColor.CurrentColor == tatgetColor;
}
public class ColorChecker : MonoBehaviour
{
public ButtonColorPair[] buttonColorPairs;
private void Awake ()
{
// Register a callback which is called every time one of the buttons
// has changed the color
foreach(var pair in buttonColorPairs)
{
// It is save to remove a callback even if it wasn't added before
// but it makes sure there is only one single callback
pair.changeColor.OnChangedColor -= HandleChangedColor;
pair.changeColor.OnChangedColor += HandleChangedColor;
// Here you would also set the targetColor if you want to do it via code e.g. like
pair.targetColor = Color.green;
// or to be save e.g. pick a random value from the color array etc
// Maybe this class would even be responsible for telling the buttons which colors are
// available at all
}
}
private void OnDestroy()
{
// As a good practice always remove callbacks as soon as you don't need them anymore
foreach(var pair in buttonColorPairs)
{
pair.changeColor.OnChangedColor -= HandleChangedColor;
}
}
// Finally check if the color matches for all buttons everytime one of them was changed
private void HandleChangedColor ()
{
// This is Linq and require "using System.Linq;" on top of your file
if(buttonColorPairs.All(p => p.IsMatching)
{
Debug.Log("All buttons are matching their target color");
}
else
{
Debug.Log("Nope not all matching yet");
}
// it basically equals doing something like
//bool allMatch = true;
//foreach(var p in buttonColorPairs)
//{
// if(!p.IsMatching)
// {
// allMatch = false;
// break;
// }
//}
//if(allMatch)
//{
// Debug.Log("All buttons are matching their target color");
//}
//else
//{
// Debug.Log("Nope not all matching yet");
//}
}
}

String value cannot be assigned through class methods

G'day, I seem to be having a problem with a class variable in my Windows Form Application.
It is a string variable in my Player class. When I create a Player object, I cannot seem to assign a string value to the playerName variable.
I've manually created my Get and Set methods and I can't see anything wrong with it.
I've gotten a message a few times that informs me that the string playerName isn't assigned and will remain its default value of null. I'm not sure why this is the case.
class Player
{
private string playerName = ""; //Variable used for giving each player a unique identifier, eg. 'Player One', etc
//public string PlayerName { get => playerName; set => playerName = value; }
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
name = this.playerName;
}
public string getPlayerName() //Returns the player's name
{
return playerName;
}
}
Creating a Player class and trying to show the playerName to a textbox doesn't work, the playerName value remains null
public partial class FrmBoardGameApp : Form
{
public FrmBoardGameApp()
{
InitializeComponent();
}
ArrayList Players = new ArrayList();
public void creationOfPlayers() //BUG!!! Values stay null
{
Player playerOne = new Player(); //Player 1
Player playerTwo = new Player(); //Player 2
playerOne.setPlayerName("Player One");
playerTwo.setPlayerName("Player Two");
Players.Add(playerOne); //Player name value is still null at this point
Players.Add(playerTwo);
}
//Here's the textbox assignment code
public bool playerTurn = true; //A bool variable used to keep track of whose turn it is
public bool setupCheck = false; // Ensures that the endturn button cannot be hit unless all setup is done
public int setupCheckValue = 0; //Ensures that the setup button can only be hit once
public void testingPlayerTurn() //A method used to test whose turn it is
{
if (setupCheck != true)
{
MessageBox.Show("Game is not setup, please setup the game");
}
else
{
//Textbox is empty, as playerName value remains null
if (playerTurn)
{
Players.ToArray();
Player firstPlayer = (Player)Players[0];
txtAns.Text = firstPlayer.getPlayerName();
/*
* This method of accessing an element and controlling/manipulating its values works
*/
}
else if (!playerTurn)
{
//playerTwo.setPlayerName(factionTwo);
//txtAns.Text = playerTwo.getPlayerName();
}
}
}
private void btnEndTurn_Click(object sender, EventArgs e) //Ends the turn of the current player
{
changePlayer();
//testingPlayerTurn();
testingPlayerNames();
}
}
I've added into the code example some methods that handle player assignment, just in case it helps in any way. But the issue starts at the creationOfPlayers method and in the Player class.
this:
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
name = this.playerName;
}
should be the other way around:
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
this.playerName = name;
}
Problem: you actually assigned the value of your field to the local parameter of the method.
Explanation: on the left side of the = should be the variable that receives the value, on the right side should be the variable that gives the value (or reference in other cases).
In your player class, inside setPlayerName method, you should be asisgning name parameter value to this.playerName. I see you have done reverse
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
//name = this.playerName;
this.playerName = name;
}

Unity - How to create a Custom Editor for dynamically change size of a list inside an other list with custom size?

I want to create a Custom Editor for Unity with a list inside a list and allow to edit the size dynamically and with a Popup inside the second list.
Here is a couple of examples of what I want:
The problem is that I can't change the second list size because it change the size of all the elements in the second list, so i can't have diferent sizes for each element of list.
Also I have the same problem with the popup, the item selected is allways the same for all the popups (as you can see in the second image).
How I can save each dimension or popup selected of each item selected without changing the others?
Here is my code:
ScriptableObject class
public class MyClass : ScriptableObject {
public class MyInsideClass
{
public List<string> data;
public MyInsideClass()
{
data = new List<string>();
}
}
public List<MyInsideClass> allData;
}
Custom Editor (Database just have the popup list names)
[CustomEditor(typeof(MyClass))]
public class TempEdior : Editor
{
int numberOfCombinations = 0;
int numberOfDataForCombination = 0;
int selected = 0;
public override void OnInspectorGUI()
{
GUILayout.BeginHorizontal();
GUILayout.Label("Number of combinations:");
numberOfCombinations = EditorGUILayout.IntField(numberOfCombinations);
GUILayout.EndHorizontal();
for (int i = 0; i < numberOfCombinations; i++)
{
GUILayout.BeginHorizontal();
GUILayout.Label("Combination " + (i + 1) + " - number of data:");
numberOfDataForCombination = EditorGUILayout.IntField(numberOfDataForCombination);
GUILayout.EndHorizontal();
for (int j = 0; j < numberOfDataForCombination; j++)
{
Rect rect = EditorGUILayout.GetControlRect();
selected = EditorGUI.Popup(new Rect(rect.x + 63, rect.y, 100, EditorGUIUtility.singleLineHeight), selected, Database.Instance.slotTypes.ToArray());
}
}
}
}
IMO you're approaching this the wrong way. It's easy to get carried away writing inspector code forever, in convoluted ways that break Unity's paradigms for little benefit. Rely on SerializedProperties as much as you can, as it will handle undo, multi-edit etc.
Basically all you want is to edit a string using a popup, right? Then just use a PropertyDrawer for this, and for the rest just let Unity's array inspector do its magic:
using System;
using UnityEditor;
using UnityEngine;
public class TestArrayOfArray : MonoBehaviour
{
[Serializable]
public struct ComboItem
{
public string value;
}
[Serializable]
public struct Combo
{
public ComboItem[] items;
}
public Combo[] combos;
}
[CustomPropertyDrawer(typeof(TestArrayOfArray.ComboItem))]
public class ComboItemDrawer : PropertyDrawer
{
static readonly string[] comboItemDatabase = { "Bla", "Bli", "Blu" };
static readonly GUIContent[] comboItemDatabaseGUIContents = Array.ConvertAll(comboItemDatabase, i => new GUIContent(i));
public override void OnGUI( Rect position, SerializedProperty property, GUIContent label )
{
property = property.FindPropertyRelative("value");
EditorGUI.BeginChangeCheck();
int selectedIndex = Array.IndexOf(comboItemDatabase, property.stringValue);
selectedIndex = EditorGUI.Popup(position, label, selectedIndex, comboItemDatabaseGUIContents);
if (EditorGUI.EndChangeCheck())
{
property.stringValue = comboItemDatabase[selectedIndex];
}
}
}
This is very crude, runtime + inspector code in a single file, fake "database" hard-coded as a static array, doesn't handle mixed values in case of a multi-edit, etc etc, but it should get you started.
--
General advice: favor arrays over Lists if you can, eg in this case the combos are static serialized data that you're never gonna resize at runtime, so an array is more efficient.
Inline field declarations when possible for readability (eg public List<string> data = new List<string>(); is fine).
Don't initialize arrays/lists that are serialized, as their default value will be overwritten by deserialization, leading to a wasted allocation.

Categories

Resources