Ill try to explain my issue. I need to do for my school project simple 2D escape room. I've got most of the scripts ready, when I click an item he add to the inventory list, and then I can see it on my inventory bar. The problem is , I want that the buttons of the UI Bar inventory, to know after I click the item , to know what item I've been clicking on. the way I did worked half way because he always give the key item. Ive got State Machine Interface. that inheritance to 2 more scripts (that open or close the other interactive stuff (door drawer). got KeyItems script and Interactable Script and UImanager - Singleton.
is there to it in a generic way?
this is the PlayerCast Script that I add the items I click on to the inventory, thought the button need to be here
public enum item {none , paperclip , key }
public class PlayerCast : MonoBehaviour
{
public Camera cam;
public List<item> inventory;
public item activeItem;
private void Start()
{
inventory = new List<item>();
}
// Update is called once per frame
void Update()
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.CompareTag("Interact"))
{
Interactable interactable = hit.collider.gameObject.GetComponent<Interactable>();
interactable.CLickMe(activeItem);
}
else if(hit.collider.CompareTag("Key"))
{
KeyItems hitItem = hit.collider.gameObject.GetComponent<KeyItems>();
hitItem.PickMeUp();
}
}
}
}
public void ButtonSelect() //this is the generic button im trying to do
{
this.activeItem = item.key;
Debug.Log("Clicked");
}
You need event.
public enum ItemTypes { None , Paperclip , Key }
[DisallowMultipleComponent]
public class Foo : MonoBehaviour {
// Action its void delegate
public event System.Action<ItemTypes> Selected;
public ItemTypes ActiveItem { get; private set; }; // property
public void ButtonSelect () {
ActiveItem = ItemTypes.Key;
Selected?.Invoke(ActiveItem); // invoke event
}
}
[DisallowMultipleComponent]
public class YourUIBar : MonoBehaviour {
[SerializeField] private Foo _foo; // link on inspector
private void OnEnable () {
ItemChange(Foo.ActiveItem); // setup current
Foo.Selected += ItemChange; // subscribe to event
}
private void OnDisable () {
Foo.Selected -= ItemChange;
}
private void ItemChange (ItemTypes item) {
// your code
}
}
Related
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;
}
}
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();
}
}
}
}
I have got displayed cards in a scene, they are gameobjects with this script:
public class CardClick : MonoBehaviour
{
DeckBuildManager deckManager;
// Start is called before the first frame update
void Start()
{
deckManager = FindObjectOfType<DeckBuildManager>();
}
// Update is called once per frame
public void AddToDeck()
{
if (deckManager.DeckCards.Count <= 9)
{
if (!deckManager.DeckCards.ContainsValue(this.gameObject.GetComponent<CardValues>().dataCard.cardName))
{
deckManager.DeckCards.Add(this.gameObject.GetComponent<CardValues>().dataCard, this.gameObject.GetComponent<CardValues>().dataCard.cardName);
}
}
}
}
Cards got a button component which use the AddToDeck function when clicked but there are impossible to click.
What can i do to click them?
I use panel to display those cards so its impossible to delete the panel in case it was the error.
resolv
public void ClickOnCard()
{
if (deckManager.DeckCards.Count <= 9)
{
if (!deckManager.DeckCards.ContainsValue(this.gameObject.GetComponent<CardValues>().dataCard.cardName))
{
deckManager.DeckCards.Add(this.gameObject.GetComponent<CardValues>().dataCard, this.gameObject.GetComponent<CardValues>().dataCard.cardName);
}
}
}
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");
//}
}
}
I'm trying to toggle. When I click on the toggle (if it is on I mean checked), it closes some gameobject, if I click on the toggle (if it is off I mean unchecked) it opens some gameobject but I don't know which function unity
toggle.onselect?
toggle.onValuesChanged? or others which one
public GameObject controlledObject;
public NamesGet nameController;
public Text text;
public Button button;
public Toggle toggle;
private bool deneme;
public void FixedUpdate()
{
text.text = controlledObject.name;
if(?????????)
{
controlledObject.SetActive(!controlledObject.activeSelf);
}
}
I would use something like this :
toggle.onValueChanged.AddListener((value) =>
{
MyListener(value);
});//Do this in Start() for example
public void MyListener(bool value)
{
if(value)
{
//do the stuff when the toggle is on
}else {
//do the stuff when the toggle is off
}
}
New
instead using OnValueChanged why not use EventTrigger - Pointer Click so it will only be called Once.
same as adding Listener via script but its more quick and handy.