How to focus inputfields while click on 'Tab' button in unity 3d - unity3d

I want focus input fields using tab button. I have found some code for focusing input fields but that one not satisfied my requirement. In my application one page design like number of input field in both horizontal and vertical formate. In my page have 3 rows of input field and each row have 3 input fields. my requirement is when user click on tab button focus next input field it's working but when reach end of the row, how can I focus next row contain input field. Please suggest any idea. Thank you. You can find my sample page design in below.
Here is the code I have tried.
Selectable next = system.currentSelectedGameObject.GetComponent<Selectable().FindSelectableOnRight();
if (next != null)
{
InputField inputfield = next.GetComponent<InputField>();
if (inputfield != null)
{
inputfield.OnPointerClick(new PointerEventData(system));
system.SetSelectedGameObject(next.gameObject, new BaseEventData(system));
}
}

I had implemented the a similar function by NGUI, you can make some modify if using UGUI. The idea is set the nextInput manually by public variable
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIInputTab : MonoBehaviour {
UIInput thisInput;
public GameObject nextInput;
void Start () {
thisInput = transform.GetComponent<UIInput>();
}
void Update () {
if (thisInput.isSelected)
{
if (nextInput != null && Input.GetKeyDown(KeyCode.Tab))
{
UICamera.selectedObject = nextInput;
}
}
}
}
Update
: Dynamic generate random number InputField and assign the next InputField.
InputfieldTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class InputfieldTest : MonoBehaviour {
public GameObject inputFieldPrefab;
public GameObject panel;
private GameObject lastInput;
EventSystem m_EventSystem;
void Start () {
m_EventSystem = EventSystem.current;
for(int i = 0; i < Random.Range(5,10);i++){
GameObject column = new GameObject();
column.transform.parent = panel.transform;
column.name = "Column" + i;
for (int j = 0; j < Random.Range(2, 8);j++){
GameObject input = Instantiate(inputFieldPrefab);
input.transform.parent = column.transform;
input.GetComponent<RectTransform>().position = new Vector3(300+ 200 * j, 300+ 200 * i, 0);
input.name = "InputField" + i + "-" + j;
// set nextInput
if(lastInput != null) {
lastInput.GetComponent<InputTabControl>().nextInput = input;
}
lastInput = input;
}
}
}
void Update () {
GameObject currentSelect = m_EventSystem.currentSelectedGameObject;
if (currentSelect != null)
{
print(currentSelect.name);
GameObject nextInput = currentSelect.GetComponent<InputTabControl>().nextInput;
if (nextInput != null && Input.GetKeyDown(KeyCode.Tab))
{
InputField inputfield = nextInput.GetComponent<InputField>();
if (inputfield != null)
{
inputfield.OnPointerClick(new PointerEventData(m_EventSystem));
m_EventSystem.SetSelectedGameObject(nextInput.gameObject, new BaseEventData(m_EventSystem));
}
}
}
}
}
InputTabControl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputTabControl : MonoBehaviour {
public GameObject nextInput;
}
Result:

Here's my solution, pretty similar.
public class TabToNextController : MonoBehaviour {
public InputField nextField;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (GetComponent<InputField> ().isFocused && Input.GetKeyDown (KeyCode.Tab)) {
nextField.ActivateInputField ();
}
}
Just add this script to the item you want to be tabbed from. Then in the GUI drop in the inputfield you want to be tabbed to as the "nextField".

I recently ran into this problem and it drove me crazy to think there was no tabs feature. I had never noticed it before. A simple solution that seems to work so far is to use the Selectable class that the UI elements inherit from.
I tested this with the most common types (for me) - InputField, Buttons and drop down lists.
It tabs to each of them as expected
use spacebar to click the button
use spacebar to open the drop down
If you want full web style controls (eg arrow keys) to invoke the drop down, you need to write that code.
public class TabToNextController : MonoBehaviour, IUpdateSelectedHandler
{
public Selectable nextField;
public void OnUpdateSelected(BaseEventData data)
{
if (Input.GetKeyDown(KeyCode.Tab))
nextField.Select();
}
}
Cheers

This is what worked for me:
public Selectable next; //if you want the cursor to start at a specific field
//you can set this in the inspector to any input field
private EventSystem system;
private int tabIndex; //to keep track of cursor
void Start()
{
system = EventSystem.current;// EventSystemManager.currentSystem;
//if you don't want to set next in the inspector, you can do it here
//next = Selectable.allSelectables[0];
tabIndex = 0;
next.Select(); //make sure to do this!
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
if (tabIndex >= Selectable.allSelectables.Count)
{
next = Selectable.allSelectables[0];
tabIndex = 0;
}
else
{
//if nothing is selected then system.currentSelectedGameObject
//will throw a nullPointer Exception
next = system.currentSelectedGameObject.GetComponent<Selectable>()
.FindSelectableOnDown();
tabIndex++;
}
Debug.Log(next);
if (next != null)
{
InputField inputfield = next.GetComponent<InputField>();
if (inputfield != null)
inputfield.OnPointerClick(new PointerEventData(system)); //if it's an input field, also set the text caret
system.SetSelectedGameObject(next.gameObject, new BaseEventData(system));
}
}
}

Related

Unity's Open XR inputs TryGetFeatureValue always 0 for all Unity XR Input's CommonUsages

So I have been debugging for hours at this point to no avail. I call my function in another class and it keeps returning 0 I have tried logging everything to see if there is an error with it, and I can't find one. The target device prints out as UnityEngine.XR.InputDevice and nothing errors or warns. please if anyone has any insight. here's how I call it
Debug.Log(RightHand.AButtonDown());
and here is my code for the functions
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public static class RightHand
{
private static InputDevice targetDevice;
static RightHand()
{
TryInitialize();
}
private static void TryInitialize()
{
Debug.Log("ran inital");
List<InputDevice> devices = new List<InputDevice>();
InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log("ran log");
Debug.Log(item.name + item.characteristics);
}
Debug.Log("right controler characteristics" + rightControllerCharacteristics);
if (devices.Count > 0)
{
targetDevice = devices[0];
}
Debug.Log(targetDevice);
}
public static bool AButtonDown()
{
targetDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool primaryButtonOut);
if (primaryButtonOut)
{
return true;
}
else
{
return false;
}
}
}
My best guess is the list of devices needs to be updated each time a device value is accessed. The method I use uses Unity's XR Toolkit and had the same problem as yours until I moved InputDevice assignment into Update.
using UnityEngine.XR;
public class InputDeviceSample : MonoBehaviour
{
InputDevice left;
InputDevice right;
void Start()
{
}
void Update()
{
// needs to be in Update
right = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
// left = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
// more https://docs.unity3d.com/ScriptReference/XR.XRNode.html
// assigns button value to out variable, if expecting Vector3 replace bool
right.TryGetFeatureValue(CommonUsages.triggerButton, out bool isPressed);
Debug.Log(isPressed);
}
}
CommonUsages is the list of input actions you can read in, here's a list with brief descriptions:
https://docs.unity3d.com/ScriptReference/XR.CommonUsages.html

Accessing variable in another script, The name 'isAnimated' does not exist in the current context

I'm trying to access a boolean from another script, which I using a custom namespace. The idea is to have a checkbox in the inspector per item if it should be animated or not. On a weaponmesh, I have a materialoffset animator that should trigger if that box is checked.
using MFPSEditor;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MFPS.Internal.Structures;
namespace MFPS.Addon.Customizer
{
At the bottom of this script, I have this:
[System.Serializable]
public class GlobalCamo
{
public string Name;
[SpritePreview(50)] public Texture2D Preview;
public MFPSItemUnlockability Unlockability;
public string Description;
public bool isAnimated;
public int Price
{
get => Unlockability.Price;
}
public bool animatedCamo()
{
return isAnimated;
}
public bool isFree() { return Price <= 0; }
[HideInInspector] public Sprite m_sprite = null;
public Sprite spritePreview()
{
if (m_sprite == null && Preview != null)
{
m_sprite = Sprite.Create(Preview, new Rect(0, 0, Preview.width, Preview.height), new Vector2(0.5f, 0.5f));
}
return m_sprite;
}
}
}
I am trying to access the variable "isAnimated" in the script below:
using UnityEngine;
public class MaterialOffsetAnimator : MonoBehaviour
{
// The material that will be animated
Material material;
// The speed at which the offset will be animated
public Vector2 animationSpeed;
void Update()
{
material = GetComponent<Renderer>().material;
if ( isAnimated == true )
{
// Update the material's offset based on the animation speed
material.mainTextureOffset += animationSpeed * Time.deltaTime;
}
else
{
}
}
}
isAnimated is an instance field in your GlobalCamo class, not a static field. You can't access it without a reference to an instance of GlobalCamo.
You can just make the isAnimated field static, like this:
public static bool isAnimated;
And then access it in MaterialOffsetAnimator like this:
if (GlobalCamo.isAnimated)
{
// Your code
}
Also, a side note: You don't need to use == true with booleans. If a boolean is true, the condition will evaluate to true.

How do i implement TMP in an unity script that normally supports unity text

I'm following a skillshare tutorial on how to make a card game, and in the tutorial normal unity text is used, but in for me the text turns out blurry. So i turned to using TextMeshPro to doing it because the text supports auto-resizing and is crisp.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
// holds the refs to all the Text, Images on the card
public class OneCardManager : MonoBehaviour {
public CardAsset cardAsset;
public OneCardManager PreviewManager;
[Header("Text Component References")]
public Text ManaCostText;
public Text PowerText;
[Header("Image References")]
public Image CardBodyImage;
public Image CardFaceGlowImage;
public Image CardBackGlowImage;
void Awake()
{
if (cardAsset != null)
ReadCardFromAsset();
}
private bool canBePlayedNow = false;
public bool CanBePlayedNow
{
get
{
return canBePlayedNow;
}
set
{
canBePlayedNow = value;
CardFaceGlowImage.enabled = value;
}
}
public void ReadCardFromAsset()
{
// universal actions for any Card
// add mana cost
ManaCostText.text = cardAsset.ManaCost.ToString();
if (cardAsset.Power != 0)
{
// this is a creature
PowerText.text = cardAsset.Power.ToString();
}
if (PreviewManager != null)
{
// this is a card and not a preview
// Preview GameObject will have OneCardManager as well, but PreviewManager should be null there
PreviewManager.cardAsset = cardAsset;
PreviewManager.ReadCardFromAsset();
}
}
}
the mana text should be able to take in a TMP object but i don't know how to do that. help please!
Use the TMPro.TMP_Text instead of UnityEngine.UI.Text.

How do I get the dropdown that called my OnListener handler?

I have an array of 13 dropdown boxes, each attached to an event in my game. I have AddListener events attached to each, but they all call the same routine. In the routine, how can I determine from which dropdown object it was called?
Thanks.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SelectSounds : MonoBehaviour
{
public TMPro.TMP_Dropdown[] dropdownSFX = new TMPro.TMP_Dropdown[13];
public Toggle[] soundToggles = new Toggle[13];
void Awake()
{
for (int i = 0; i < 13; i++)
{
var temp = i;
dropdownSFX[i].onValueChanged.AddListener(delegate { DropdownValueChanged(temp); }); // This works fine on value change
dropdownSFX[i].OnPointerClick.AddListener(delegate { DropdownValueChanged(temp); });
// Gets error: TMP_Dropdown.OnPointerClick(PointerEventData)' is a method, which is not valid in the given context
}
private void DropdownValueChanged(int newPosition)
{
// Get the value of the dropdown, and from which dropdown it came?
Debug.Log("In DropdownVC: " + newPosition);
lastSelected = newPosition;
}
}
You can have a method which takes a DropDown parameter.
public void DropdownValueChanged(Dropdown dropDown)
{
Debug.Log(dropDown.value); // Gives the index of the selected item
Debug.Log(dropDown.options[dropDown.value].text); // Gives the text of the selected item
}
This method will give you the reference to the DropDown. And you can access to its value as I show in the method.
Now subscribe to the OnValueChanged as the following
At the very end of the picture, you can add reference to DropDown, and it will be passed to the method when called. Drag and drop the component to the circled zone :

Simplest way to carry dialogue between scenes without use of Player Prefs, etc

I have been working on a dialogue system for my game and I was wondering if anyone knows how to keep the system between different scenes. I know you can use things such as Player Prefs but for one, I do not understand it and upon research, people do not generally recommend it for storing large complicated things. I managed to get close to doing so by using dontDestroy just as you would with a character, however, it did not work completely as the button to switch to the next line of text, of course, broke along with the singleton I created for my system. What would be the best way for me to go about this?
Here is all of my code just in case it is needed:
Making the scriptable object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Dialogue", menuName = "Dialogues")]
public class Dialogue : ScriptableObject
{
[System.Serializable]
public class Info
{
public string myName;
public Sprite portrait;
[TextArea(4, 8)]
public string mytext;
}
[Header("Insert Dialogue Info Below")]
public Info[] dialogueInfoSection;
}
Main code for system (sigleton breaks here while switching scenes):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MainDialogueManager : MonoBehaviour
{
public static MainDialogueManager instance;
private void Awake()
{
if(instance != null)
{
Debug.LogWarning("FIX THIS" + gameObject.name);
}
else
{
instance = this;
}
}
public GameObject DialogueBoX;
public Text dialogueNameofChar;
public Text characterSays;
public Image characterPortrait;
private float textDelay = 0.005f;
public Queue<Dialogue.Info> dialogueInfoSection = new Queue<Dialogue.Info>();
public void EnqueueDialogue(Dialogue db)
{
DialogueBoX.SetActive(true);
dialogueInfoSection.Clear();
foreach(Dialogue.Info info in db.dialogueInfoSection)
{
dialogueInfoSection.Enqueue(info);
}
DequeueDialogue();
}
public void DequeueDialogue()
{
if (dialogueInfoSection.Count==0)
{
ReachedEndOfDialogue();
return; /////
}
Dialogue.Info info = dialogueInfoSection.Dequeue();
dialogueNameofChar.text = info.myName;
characterSays.text = info.mytext;
characterPortrait.sprite = info.portrait;
StartCoroutine(TypeText(info));
}
IEnumerator TypeText(Dialogue.Info info)
{
characterSays.text= "";
foreach(char c in info.mytext.ToCharArray())
{
yield return new WaitForSeconds(textDelay);
characterSays.text += c;
yield return null;
}
}
public void ReachedEndOfDialogue()
{
DialogueBoX.SetActive(false);
}
}
Dialogue Activation:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainDialogueActivation : MonoBehaviour
{
public Dialogue dialogue;
public void startActivationofDialogue()
{
MainDialogueManager.instance.EnqueueDialogue(dialogue);
}
private void Start()
{
startActivationofDialogue();
}
}
Go to next dialogue line:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainDialogueButtons : MonoBehaviour
{
public void GoToNextDialogueLine()
{
MainDialogueManager.instance.DequeueDialogue();
}
}
How about something like this?
The idea is pretty similar to what you're doing, with a few tweaks:
I'm storing the active dialog in a scriptable object (DialogueSystem) so that it can persist between scenes. Each time I load a new scene, I check if there's an active dialog, and if I so I show the dialog popup in Start().
Whereas you remove the dialog section that you're currently showing to the player from the current dialog, I don't remove the current section until the player clicks to the next section. That's necessary because you may need to re-show the same section if you move to a new scene.
Make sure to create an instance of the DialogueSystem scriptable object and assign it to MainDialogueActivation and MainDialogManager
MainDialogActiviation has some testing code in it so you can hit a key to start a new dialog or switch between scenes.
MainDialogueActiviation.cs
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainDialogueActivation : MonoBehaviour
{
public Dialogue dialogue;
// This scriptable object stores the active dialog so that you
// can persist it between scenes
public DialogueSystem dialogSystem;
private void Start()
{
// If we had an active dialog from the previous scene, resume that dialog
if (dialogSystem?.dialogInfoSections.Count > 0)
{
GetComponent<MainDialogueManager>().ShowDialog();
}
}
private void Update()
{
// Pressing D queues and shows a new dialog
if (Input.GetKeyDown(KeyCode.D))
{
GetComponent<MainDialogueManager>().EnqueueDialogue(this.dialogue);
}
// Pressing C ends the current dialog
if (Input.GetKeyDown(KeyCode.C))
{
this.dialogSystem.dialogInfoSections.Clear();
GetComponent<MainDialogueManager>().ReachedEndOfDialogue();
}
// Pressing S swaps between two scenes so you can see the dialog
// persisting
if (Input.GetKeyDown(KeyCode.S))
{
if (SceneManager.GetActiveScene().name == "Scene 1")
{
SceneManager.LoadScene("Scene 2");
}
else if (SceneManager.GetActiveScene().name == "Scene 2")
{
SceneManager.LoadScene("Scene 1");
}
}
}
}
MainDialogueManager.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class MainDialogueManager : MonoBehaviour
{
// This scriptable object stores the active dialog
public DialogueSystem dialogSystem;
public GameObject DialogueBox;
public Text dialogueNameofChar;
public Text characterSays;
public Image characterPortrait;
private float textDelay = 0.005f;
// The game object for the dialog box that is instantiated in this
// scene
private GameObject dialogBoxGameObject;
/// <summary>
/// Shows the dialog window for the dialog that is in this object's
/// dialogSystem property.
/// </summary>
public void ShowDialog()
{
// Instantiate the dialog box prefab
this.dialogBoxGameObject = Instantiate(this.DialogueBox);
// I'd recommend putting a script on your "dialog box" prefab to
// handle this stuff, so that this script doesn't need to get a
// reference to each text element within the dialog prefab. But
// this is just a quick and dirty example for this answer
this.dialogueNameofChar = GameObject.Find("Character Name").GetComponent<Text>();
this.characterSays = GameObject.Find("Character Text").GetComponent<Text>();
this.characterPortrait = GameObject.Find("Character Image").GetComponent<Image>();
// If you have multiple response options, you'd wire them up here.
// Again; I recommend putting this into a script on your dialog box
GameObject.Find("Response Button 1").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);
GameObject.Find("Response Button 2").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);
ShowDialogSection(this.dialogSystem.dialogInfoSections.Peek());
}
/// <summary>
/// Puts a dialog into this object's dialogSystem property and
/// opens a dialog window that will show that dialog.
/// </summary>
public void EnqueueDialogue(Dialogue db)
{
foreach (Dialogue.Info info in db.dialogueInfoSection)
{
this.dialogSystem.dialogInfoSections.Enqueue(info);
}
ShowDialog();
}
/// <summary>
/// Removes the dialog section at the head of the dialog queue,
/// and shows the following dialog statement to the player. This
/// is a difference in the overall logic, because now the dialog
/// section at the head of the queue is the dialog that's currently
/// being show, rather than the previous one that was shown
/// </summary>
public void ShowNextDialogSection()
{
this.dialogSystem.dialogInfoSections.Dequeue();
if (this.dialogSystem.dialogInfoSections.Count == 0)
{
ReachedEndOfDialogue();
return;
}
Dialogue.Info dialogSection = this.dialogSystem.dialogInfoSections.Peek();
ShowDialogSection(dialogSection);
}
/// <summary>
/// Shows the specified dialog statement to the player.
/// </summary>
public void ShowDialogSection(Dialogue.Info dialogSection)
{
dialogueNameofChar.text = dialogSection.myName;
characterSays.text = dialogSection.mytext;
characterPortrait.sprite = dialogSection.portrait;
StartCoroutine(TypeText(dialogSection));
}
IEnumerator TypeText(Dialogue.Info info)
{
characterSays.text = "";
foreach (char c in info.mytext.ToCharArray())
{
yield return new WaitForSeconds(textDelay);
characterSays.text += c;
yield return null;
}
}
public void ReachedEndOfDialogue()
{
// Destroy the dialog box
Destroy(this.dialogBoxGameObject);
}
}
DialogSystem.cs
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Dialogues/Dialog System")]
public class DialogueSystem : ScriptableObject
{
public Queue<Dialogue.Info> dialogInfoSections = new Queue<Dialogue.Info>();
}
Here's what my dialog box prefab looks like
Every scene needs an object (presumably a prefab to make it easy to add to every scene) that has MainDialogActiviation and MainDialogManager on it. Mine looks like this:
This might be a bit of an unpopular opinion but using Singleton's are fine. It's just that MonoBehaviour singletons are tricky, you can use Object.DontDestroyOnLoad(instance). But things get ugly because it doesn't get destroyed when the scene changes (good) but if you go back to the scene it will load another one (bad). There's a few ways to get around that like having the object destroy itself if there's already an instance or having a subscene.
I would suggest not using MonoBehaviour singletons and use ScriptableObject singletons. You can lazy instantiate by putting the asset in a resource folder and use Resource.Load like this.
public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableSingleton<T> {
private static string ResourcePath {
get {
return typeof(T).Name;
}
}
public static T Instance {
get {
if (instance == null) {
instance = Resources.Load(ResourcePath) as T;
}
return instance;
}
}
private static T instance;
}
With this code you create a Singleton class say DialogueManager you create a DialogueManager.asset for it and put it in a "Resources" folder.