How can I create a Button (Not GUI etc) in Unity 4.6 from code? I need to be able to specify the action when tapped.
I tried adding a delegate or something to onClick but I can't get it to work.
http://docs.unity3d.com/ScriptReference/UI.Button-onClick.html
This is the code I have so far:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class ButtontitleAndAction {
public string Title;
public delegate void Del();
public ButtontitleAndAction(string title, Del theaction) {
this.Title = title;
this.action = theaction;
}
public void Call() {
Debug.Log("Call " + this.Title);
this.action();
}
private Del action;
}
public class MjalertScript : MonoBehaviour {
public Text Text;
public GameObject gameobjectButtons;
public GameObject gameobjectMjalertbutton;
// Use this for initialization
void Start() {
}
public void Addbuttons(ButtontitleAndAction[] buttontitles) {
// public void Addbuttons(string[] buttontitles) {
GridLayoutGroup grid = this.gameobjectButtons.GetComponent<GridLayoutGroup>();
float newwidth = 400 / buttontitles.Length;
grid.cellSize = new Vector2(newwidth, grid.cellSize.y);
foreach (ButtontitleAndAction buttondata in buttontitles) {
GameObject gameobjectButton = (GameObject)Instantiate(this.gameobjectMjalertbutton);
Transform child = gameobjectButton.transform.GetChild(0); // There should just be one child, the gameobject for the Text label.
Text textofbutton = child.gameObject.GetComponent<Text>();
textofbutton.text = buttondata.Title;
gameobjectButton.transform.SetParent(gameobjectButtons.transform, false);
Button buttonofbutton = gameobjectButton.GetComponent<Button>();
Debug.Log("buttonofbutton: " + buttonofbutton + ", for label text: " + buttondata.Title);
buttonofbutton.onClick.AddListener(() => {
Debug.Log("DOO Something!");
//buttondata.Call();
});
}
}
}
The buttons don't ever react to any taps. I'd expected "DOO Something!" something to be output but nothing.
The code above has no problems actually. I figured out the real problem. Somehow, an EventSystem item was never added to the hierarchy. Just add one and buttons will work. Actually in my case, neither buttons through code or using the editor ever worked. The lack of the EventSystem was the problem.
Related
I have a singleton class called KeyManager that is used to define and change player keybinds. It needs references to Text objects that will display what the currently set Keybind is. The singleton and text objects are in different scenes so I want to set the object definitions using a separate script that is attached to the (parent of) the Text objects themselves.
Here is the Singleton in its basic implementation.
public class KeyManager : MonoBehaviour
{
public static KeyManager Instance { get; private set; }
public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();
private GameObject currentKey;
//the Text objects that need references
public static Text PitchUp { get { return pitchUp; } set { pitchUp = value; } }
private static Text pitchUp;
public static Text PitchDown { get { return pitchDown; } set { pitchDown = value; } }
private static Text pitchDown;
public static Text RotateLeft { get { return rotateLeft; } set { rotateLeft = value; } }
private static Text rotateLeft;
public static Text RotateRight { get { return rotateRight; } set { rotateRight = value; } }
private static Text rotateRight;
//...irrelevant singleton logic continues
}
Here is the script attached to the parent GameObject of each Text object to be referenced (parent is a button). The GameObject name of the button matches the Singleton property names.
public class KeybindCRData : MonoBehaviour
{
string buttonName;
Text childText;
void Start()
{
buttonName = this.name; //collect name of GameObject as set in Editor as a string
childText = this.transform.GetChild(0).GetComponent<Text>(); // collect Text object that will be assigned
//set Singleton property to this objects child Text
KeyManager.-INSERT PROPERTY NAME HERE- = childText;
//KeyManager.(Text)buttonName = childText;
// ^ this doesn't work of course, but this is the idea/goal
}
}
What is needed so I can dynamically change which property of the Singleton I am referencing based on the name of the Text object? I want to be able to just slap the KeybindCRData script on each button and be done.
The alternative that I see is specifically defining the property and related button for each keybind, but with 30+ keybinds it would not be ideal to end up with 30 different scripts each tailored to only 1 button.
Thanks for any help.
Thanks for the previous replies, they show other ways to do this more efficiently.
I figured out how to answer the crux of my original question though, so here is the code if it is useful to anyone else.
In the Manager Singleton, I add this public method.
public void SetProperty(string propertyName, TextMeshProUGUI value)
{
this.GetType().GetProperty(propertyName).SetValue(this, value);
}
In the KeybindCRData script that is attached to each individual button(used to rebind), I added this method call to the Awake() function.
void Awake()
{
//collect name of GameObject as set in Editor as a string
buttonName = this.name;
// collect Text object that will be assigned
childText = this.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
//set Singleton property to this objects child Text
KeyManager.Instance.SetProperty(buttonName, childText);
}
This allows me to "spell out" the Property value using a String, grabbed from the matching Button-name. Thank you everyone, cheers!
I would try to redesign the code, so that your manager does not need to know about all the different text components. To keep your manager code simple and focused, it's better to invert the flow and have all your text components subscribe to an event in the manager to get notified whenever a key binding changes.
public static class KeyBindings
{
public static event Action<string, KeyCode> Changed;
private static readonly Dictionary<string, KeyCode> bindings = new Dictionary<string, KeyCode>();
public static KeyCode Get(string name) => bindings[name];
public static void Set(string name, KeyCode key)
{
bindings[name] = key;
Changed?.Invoke(name, key);
}
}
public class KeyBindingDisplayer : MonoBehaviour
{
[SerializeField]
private string keyBindingName;
private void Reset() => keyBindingName = name;
private void OnEnable()
{
KeyBindings.Changed += OnKeyBindingsChanged;
UpdateText(KeyBindings.Get(keyBindingName));
}
private void OnDisable()
{
KeyBindings.Changed -= OnKeyBindingsChanged;
}
private void OnKeyBindingsChanged(string name, KeyCode keyCode)
{
UpdateText(keyCode);
}
private void UpdateText(KeyCode keyCode)
{
GetComponent<Text>().text = keyCode.ToString();
}
}
Instead of creating new property for each Text object, use a Dictionary<string, Text>. The Key can be the buttonName and the value can be childText.
public class KeyManager : MonoBehaviour
{
public static KeyManager Instance { get; private set; }
public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();
private GameObject currentKey;
//Key: buttonName (pitchUp, pitchDown, rotateLeft.. etc )
public static Dictionary<string, Text> Texts;
}
In KeybindCRData class, set the value to the dictionary.
public class KeybindCRData: MonoBehaviour
{
string buttonName;
Text childText;
void Start()
{
buttonName = this.name;
childText = this.transform.GetChild(0).GetComponent<Text>();
//Add appropriate logic as per requirement
if(!KeyManager.Texts.ContainsKey(buttonName)) {
KeyManager.Texts.Add(buttonName, childText)
}
}
}
I'm trying to make it so that once you click "Accept" on a UI button that it will store all the values from the quest into the following script:
[System.Serializable]
public class DataHolder
{
//Active Quest
public bool isActive;
public string title;
public string description;
public int goldReward;
public QuestGoal goal;
}
The script that stores it is here:
public class QuestGiver : MonoBehaviour
{
public Quest quest;
public DataHolder data;
public GameObject questWindow;
public Text titleText;
public Text descriptionText;
public Text goldText;
public void OpenQuestWindow()
{
Debug.Log("Quest Window Opened");
questWindow.SetActive(true);
titleText.text = quest.title;
descriptionText.text = quest.description;
goldText.text = quest.goldReward.ToString();
}
public void AcceptQuest()
{
questWindow.SetActive(false);
data.isActive = true;
data.title = quest.title;
data.description = quest.description;
data.goldReward = quest.goldReward;
}
}
and the script that is in the scene so that when a button is pressed (Tab) it will pop up a UI that will show what the current quest is, which should be stored in the first "DataHolder" script:
public class QuestChecker : MonoBehaviour
{
DataHolder data;
public GameObject questWindow;
public Text titleText;
public Text descriptionText;
public Text goldText;
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
CheckQuest();
}
}
public void CheckQuest()
{
questWindow.SetActive(true);
titleText.text = data.title;
descriptionText.text = data.description;
goldText.text = data.goldReward.ToString();
}
public void CloseQuestWindow()
{
questWindow.SetActive(false);
}
}
The reason I want it stored in "DataHolder" is because I want it to be stored between scenes. I believe my main problem is not knowing how to properly reference the "DataHolder" script as Unity gives me the warning:
Assets\Scripts\QuestChecker.cs(8,13): warning CS0649: Field 'QuestChecker.data' is never assigned to, and will always have its default value null
Any help would be appreciated. I'm pretty new to programming, please let me know if this won't work for what I want it to do and need to do it another way or if you need more info. Thanks
If all you want to do is store some data between scenes, you could simply do this:
public static class DataHolder
{
//Active Quest
public static bool isActive;
public static string title;
public static string description;
public static int goldReward;
public static QuestGoal goal;
}
Then just reference it as DataHolder.isActive etc. Note that you're not actually "saving" the data anywhere (serialising). The SerializableAttribute just indicates that the item CAN be serialised. But, this WILL store data between scenes.
Is it the best way to do so? Well, if it's just some simple data you need to store between scenes, then there's nothing wrong with doing it this way. And it'll probably fit in nicely with what you've already programmed.
You're never actually creating an instance of DataHolder.
Somewhere along the line you need a new DataHolder() that is assigned to data before trying to set any of its properties.
You need to create a relationship between QuestGiver and QuestChecker
eg.
public class QuestChecker : MonoBehaviour
{
public QuestGiver giver;
}
Now drag your QuestGiver game object onto the QuestChecker.giver field in the inspector. Then you are able to access your saved data.
public void CheckQuest()
{
titleText.text = saver.data.title;
}
I am working on a tycoon game and I want to have several options on each building the player might click on. A shop should have an Eat, Invest, and Buy-off option while an office should have something like Work, Talk to Manager, etc. To do this I created one panel for the options and spawn in the number of options per building from a list of strings which instantiates a button for each string and assigns the button text to that particular string as such:
public override void Interact()
{
Debug.Log(type);
SetUpFunctions(activites);
}
public void SetUpFunctions(List<string> activs)
{
foreach (string item in activs)
{
optionsUI.AddOption(item);
}
}
The list of activities is assigned in the inspector. This method works perfectly and assigns each activity string to the button text. However, all the buttons created all have the same function assigned to them, so I decided to do the same thing with a list of methods and assign them to each button as I did with the strings:
[HideInInspector]public List<UnityAction> functions; //list of functions tied to each activity
public override void Interact()
{
Debug.Log(type);
functions.Add(Eat);
functions.Add(Work);
functions.Add(StopWork);
SetUpFunctions(activites, functions);
}
public void SetUpFunctions(List<string> activs, List<UnityAction> funcs)
{
//foreach (string item in activs)
//{
// optionsUI.AddOption(item);
//}
var col = activs.Zip(funcs, (x, y) => new { X = x, Y = y });//combine the two lists as one
foreach (var entry in col)
{
optionsUI.AddOption(col.X, col.Y);//Assign the activity name and function
}
}
public void Eat()
{
Debug.Log("Player is eating");
}
public void Work()
{
Debug.Log("Player is Working");
}
public void StopWork()
{
Debug.Log("Player Stopped Working");
}
//From the OptionsUI script
public void AddOption(string optionName, UnityAction optionFunc)
{
OptionUIItem emptyOption = Instantiate(itemProfile, scrollViewContent);
emptyOption.transform.SetParent(scrollViewContent);
emptyOption.SetupOption(optionName, optionFunc);
}
//The script assigned to each button created
public class OptionUIItem : MonoBehaviour
{
public Text text;
public Button btn;
public void SetupOption(string name, UnityAction func)
{
text.text = name;
btn.onClick.AddListener(func);
}
public void OnOptionSelect()
{
Debug.Log("Option selected");
}
}
However, when I try doing it this way I get a NullReferenceException on the first function.Add(Eat);
My question is
Is it possible or even right to do it this way?
If so how do I resolve this in order to assign each function on the list to each button created and
any other methods that might be better to get this done like maybe using a dictionary?
Thank you.
If you hide a field in the Inspector it is not automatically initialized (only serialized fields are), thus the NullReferenceException when trying to Add something to it.
You should be sure to do this already together with the declaration
public List<UnityAction> functions = new List<UnityAction>();
I'm working on a simple mobile app game using c# and Xamarin.Forms and have gotten stuck trying to use an object class called Player.
class Player
{
private int level;
public int Level
{
get { return Level; }
set { Level = value; }
}
public Player()
{ //variables initialized
this.Level = 1;}
}
The ContentPage that creates the UI also creates an instance of Player, but when any property in Player is accessed, the app crashes.
Right now I have Player player as a data member in BasePage (the ContentPage class), and the accessing of player happens when the user presses a button:
class BasePage : ContentPage
{
private Player player;
public BasePage() {
player= new Player();
//stuff...
var btnPlayer = new Button
{
Text = "YOU"
};
btnPlayer.Clicked += (o, e) =>
{
//change text in lblMessage
lblMessage.Text = "Your level: " + player.Level;
};
//stuff is added to screen, not relevant
}
How should I be creating player so it doesn't crash? I've been checking out other questions on here and I haven't been able to find what I'm looking for (I've tried binding the object as well, but I might not be doing it correctly).
This code causes an infinitely recursive loop - the public Level (capital 'L') property is returning the value of the public Level property, which causes it to call the 'get' again, repeatedly
public int Level
{
get { return Level; }
set { Level = value; }
}
instead you should use the private member variable level (small 'l')
public int Level
{
get { return level; }
set { level = value; }
}
You could also do this, which will allow C# to create the property for you without explicitly creating a private variable.
public int Level { get; set; }
I have trouble with my custom Widget in Xamarin-Studio, where I use Mono, C# and Gtk#.
It should show 2 images and 2 label, should work like a Button and should be hide-able.
This is my code:
using System;
using GLib;
using Gtk;
using System.Runtime.InteropServices;
namespace myProgram_V1
{
[System.ComponentModel.ToolboxItem (true)]
public partial class myWidget : Gtk.Bin
{
public myWidget ()
{
this.Build ();
HeightRequest = 128;
}
public void setHeight (int height)
{
HeightRequest = height;
}
public void setLabel1 (String text)
{
label1.Text = text;
}
public void setLabel2 (String text)
{
label2.Text = text;
}
public void setImage1 (String image)
{
image1.Pixbuf = new Gdk.Pixbuf (image);
}
public void setImage2 (String image)
{
image2.Pixbuf = new Gdk.Pixbuf (image);
}
[Signal ("clicked")]
public event EventHandler Clicked {
add {
Signal signal = Signal.Lookup (this, "clicked");
signal.AddDelegate (value);
}
remove {
Signal signal = Signal.Lookup (this, "clicked");
signal.RemoveDelegate (value);
}
}
}
}
If I use this widget in the GUI-Designer of other widgets/windows like this, it is recognized by the Designer and I can set a Clickevent in the properties, but if I run the program, I get
(myProgram_V1:914): GLib-GObject-WARNING **: gsignal.c:2375: signal `clicked' is invalid for instance `0x7a9c9980' of type `__gtksharp_2_myProgram+5fV1_myWidget'
and it is not clickable.
But if I let it inherit from Gtk.Button instead of Gtk.Bin the widget is not usable with the Designer, but it can be used during runtime, if I set the click event beforehand and sometimes, during building or compiling, all global::myProgram_V1.myWidget entries in the auto generated file by the GUI-Designer are replaced with void and nothing will work anymore.
Also, .Hide() won't work with this widget and the only option to 'hide' this widget is to use .Destroy().
Thank you in advance for your help.