I have 26 images (GameObjects) where each image has a button.
If a button is clicked, it plays a sound and animation.
However, I have a problem. I have tried to prevent this, but if I perform a multi touch / stress test, some buttons cannot be clicked anymore. If I click slowly it works fine, but if I perform multiple clicks on a button then it will fail/can't be clicked anymore. Any idea why?
My source code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using DG.Tweening;
[RequireComponent(typeof(AudioSource))]
public class z_abcLetter_Main : MonoBehaviour {
public GameObject InputData;
public List<GameObject> List_uGUI;
private List<Sprite> List_Sprite;
private List<AudioClip> List_AudioClip;
private int currentIndex;
private GameObject currentGameObject;
private GameObject parentGameObject;
// Use this for initialization
void Start () {
List_Sprite = InputData.GetComponent<z_abcLetter_InputData>().Daftar_element;
List_AudioClip = InputData.GetComponent<z_abcLetter_InputData>().Daftar_Suara;
int total_element = List_Sprite.Count;
for (int i = 0; i < total_element; i++)
{
List_uGUI[i].GetComponent<UnityEngine.UI.Image>().sprite = List_Sprite[i];
List_uGUI[i].GetComponent<UnityEngine.UI.Image>().preserveAspect = true;
}
}
public void onButtonClick()
{
currentIndex = int.Parse(EventSystem.current.currentSelectedGameObject.transform.parent.name.ToString());
currentGameObject = EventSystem.current.currentSelectedGameObject;
parentGameObject = currentGameObject.transform.parent.gameObject;
StartCoroutine(PlaySoundWithAnimation());
}
IEnumerator PlaySoundWithAnimation()
{
currentGameObject.GetComponent<UnityEngine.UI.Button>().enabled = false;
currentGameObject.GetComponent<UnityEngine.UI.Button>().interactable = false;
AudioSource audio = GetComponent<AudioSource>();
audio.clip = List_AudioClip[currentIndex];
audio.Play();
parentGameObject.transform.DOPunchScale(new Vector3(2, 2, 0), 1, 1, 1);
yield return new WaitForSeconds(1.5f);
currentGameObject.GetComponent<UnityEngine.UI.Button>().enabled = true;
currentGameObject.GetComponent<UnityEngine.UI.Button>().interactable = true;
}
// Update is called once per frame
void Update () {
}
}
Your code seems to behave as expected. But for fast multiple clicks, you need to reset your coroutine.
To do that, you need a reference to PlaySoundWithAnimation.
private IEnumerator coroutine;
void Start()
{
coroutine = PlaySoundWithAnimation();
}
public void onButtonClick()
{
StopCoroutine(coroutine);
// you also need to stop audio here
//...
StartCoroutine(coroutine);
}
see: https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html
The reason some buttons are not responding is because the coroutines are stacked and they're all waiting for yield return new WaitForSeconds(1.5f);
see: https://answers.unity.com/questions/309613/calling-startcoroutine-multiple-times-seems-to-sta.html
Note:
If you don't want to stop the clip, consider audio.clip.length before enabling the buttons.
Related
I have a scene with multiple triggers and a Player with collider. My idea is to have one script to deal with all interactions between triggers and Player collider. I thought that using the same script to different GameObjects would be enough but this thing doesn't work properly. While working with one trigger I need to change one GameObject attached to this trigger but the other GameObjects with this script are changing as well.
One more detail about script is that I have two types of triggers: one is for GameObjects that need some input from user, some doesn't need it. I check it by checking two fields firstChoiceText and secondChoiceText. And if they are empty, the interaction with GameObject doesn't need any choice from the player. Otherwise, I show a plane with two option and change the text of this options using firstChoiceTextfield and secondChoiceTextfield.
So the main idea of script is that when the Player enter the trigger the script activates E key of keyboard. And if we press the E button and if this interaction wasn't done yet, we go further. We check the type of trigger and if we need to make a choice, we activate so called choiceMode of PlayerScript. PlayerScript is just a script for movements of the player, and we need choiceMode just to use W and S keys to select the choice. We make our choice and return to usual movement of the player. If we don't need to make a choice we just do some action and mark the trigger as isDone.
Everything works great with one trigger but when I use several triggers and GameObjects with InteractionScript, all this triggers are mixed up. While working with one trigger I accidentally change the other script. And I don't quite understand how to separate this scripts from each other.
Can anybody give me some piece of advice?
using UnityEngine;
using UnityEngine.UI;
public class InteractionScript : MonoBehaviour
{
[SerializeField]
private GameObject buttonE;
[SerializeField]
private GameObject choiceMenu;
[SerializeField]
private PlayerScript ps;
private int choiceNum = 0;
[SerializeField]
private Transform choicePoint;
[SerializeField]
private Text firstChoiceTextfield;
[SerializeField]
private Text secondChoiceTextfield;
[SerializeField]
private string firstChoiceText;
[SerializeField]
private string secondChoiceText;
[SerializeField]
private bool enteredMe;
[SerializeField]
private bool isDone;
void OnTriggerEnter2D() {
if (!isDone) {
enteredMe = true;
buttonE.SetActive(true);
Debug.Log("++");
}
}
void OnTriggerExit2D() {
if (!isDone) {
buttonE.SetActive(false);
enteredMe = false;
}
}
void Update() {
if (!isDone) {
if (Input.GetKeyDown(KeyCode.E) && buttonE.activeSelf && enteredMe) {
if (firstChoiceText != "" && secondChoiceText != "") {
choiceMenu.SetActive(true);
buttonE.SetActive(false);
ps.choiceMode = true;
Debug.Log("with choice");
} else {
Debug.Log("without choice");
//Action
buttonE.SetActive(false);
isDone = true;
}
}
}
if (ps.choiceMode) {
firstChoiceTextfield.text = firstChoiceText;
secondChoiceTextfield.text = secondChoiceText;
if (Input.GetKeyDown(KeyCode.W)) {
choiceNum += 1;
}
if (Input.GetKeyDown(KeyCode.S)) {
choiceNum -= 1;
}
if (choiceNum > 1) {
choiceNum = 0;
} else if (choiceNum < 0) {
choiceNum = 1;
}
if (choiceNum == 0) {
choicePoint.localPosition = new Vector3(0f, 24f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice1");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
} else if (choiceNum == 1) {
choicePoint.localPosition = new Vector3(0f, -21.5f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice2");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
}
}
}
}
I'm learning Unity but my key press isn't being detected
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
Debug.Log(isJumpPressed);
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
}
why isJumpPressed always false. What am I doing wrong? From what I understand this should work but I'm obviously missing something
UPDATE:
Thanks to everyone who proposed ideas. I got the isJumpPressed to return true when I stopped trying to detect the space bar.
isJumpPressed = Input.GetKeyDown("a");
anyone got any ideas why this would work and not
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
or
isJumpPressed = Input.GetKeyDown("space");
UPDATE 2:
Apparently this is a bug in Linux. I've read it won't happen when the game is built just in the editor. I've found a workaround at
https://forum.unity.com/threads/space-not-working.946974/?_ga=2.25366461.1247665695.1598713842-86850982.1598713842#post-6188199
If any Googler's Stumble upon this issue reference the following code because this is working for me.
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(SpacebarKey());
if(isJumpPressed)
{
Debug.Log(isJumpPressed);
}
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
public static KeyCode SpacebarKey() {
if (Application.isEditor) return KeyCode.O;
else return KeyCode.Space;
}
}
The problem is that FixedUpdate and Update are not called one after the other. Update is called once per frame and FixedUpdate is called once per physics update (default is 50 updates per second).
So the following could happen:
Update is called -> GetKeyDown is true (this frame only) -> isJumpPressed = true
Update is called -> GetKeyDown is false -> isJumpPressed = false
FixedUpdate is called -> isJumpPressed is false
Here is an example that prints "Update Jump" every time you press space, and "FixedUpdate Jump" only sometimes when you press space. Do not do this:
bool isJumpPressed;
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
if (isJumpPressed)
{
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
}
}
Do this instead:
bool isJumpPressed;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
isJumpPressed = true;
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
isJumpPressed = false;
}
}
One possible problem is that your project might be using the new Input System package. If you are using this package, the old Input Manager functions will not work. To check this, go to Edit > Project Settings... > Player > Other Settings and Active Input Handling should be set to Input Manager (Old) (or Both might also work).
If you actually want to use the Input System package, you have to install it if you don't already have it, and you would check if the spacebar is pressed like so:
using UnityEngine.InputSystem;
...
jumpPressed = Keyboard.current.space.wasPressedThisFrame;
I had the same issue. Spend some hours trying to figure out.
Maybe all was good from the beginning until I figured out.
When you I executed the play to see the result and test the movement, by mistake I chose the scene tab. If I want to see the result (to play) I must click the "Game Tab". In Game Tab the keys were detected.enter image description here
I want to include a cool down function to my skill button in my mobile game that I am currently developing. So I wanted to allow the player to only be able to use the skill button once every few seconds.
Left button is my default skill button and right one is a duplicate. The default skill button will be placed over the duplicate so when I run the game, upon clicking on the default skill button, the duplicate will overlap the default skill button.
However, in my case the duplicate is not able to overlap the default skill button so it does not show the cool down timer.
I am wondering if I need to include a set of codes to allow the default skill button to turn inactive upon clicking or do I just have to sort the layers?
My current codes are as such:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Abilities : MonoBehaviour
{
public Image abilityImage1;
public float cooldown = 5;
bool isCooldown = false;
public Button ability1;
void Start()
{
abilityImage1.fillAmount = 0;
ability1.onClick.AddListener(AbilityUsed);
}
private void AbilityUsed()
{
if (isCooldown)
return;
isCooldown = true;
abilityImage1.fillAmount = 1;
StartCoroutine(LerpCooldownValue());
}
private IEnumerator LerpCooldownValue()
{
float currentTime = 0;
while (currentTime < cooldown)
{
abilityImage1.fillAmount = Mathf.Lerp(1, 0, currentTime /
cooldown);
currentTime += Time.deltaTime;
yield return null;
}
abilityImage1.fillAmount = 0;
isCooldown = false;
}
}
The first picture is my original skill button and on the bottom is a duplicate which I made to adjust the color to create an overlay effect for the cool down.
Thanks!
Use Lerp instead of changing fill amount in Update() method
void Start()
{
abilityImage1.fillAmount = 0;
ability1.onClick.AddListener(AbilityUsed);
}
private void AbilityUsed()
{
if (isCooldown)
return;
isCooldown = true;
abilityImage1.fillAmount = 1;
StartCoroutine(LerpCooldownValue());
}
private IEnumerator LerpCooldownValue()
{
float currentTime = 0;
while (currentTime < cooldown)
{
abilityImage1.fillAmount = Mathf.Lerp(1, 0, currentTime / cooldown);
currentTime += Time.deltaTime;
yield return null;
}
abilityImage1.fillAmount = 0;
isCooldown = false;
}
I am a beginer and have a problem with coding Unity. I want to show and hide 3D text meshes using two arrow keys. one mesh at once. but it doesn't work as I thought. I think you will easily find out what problem is.
and one more question. It will be worked at mobile device and dozens of meshes are going to be used. I'm not sure this is the best way, so please tell me if there is better. (especially about performance and optimization issue) thanks to read.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TextmeshSelection : MonoBehaviour{
public List <GameObject> Textmeshes = new List<GameObject> ();
public int Num;
void Update() {
if (Input.GetKeyDown(KeyCode.RightArrow)) {
Num++;
Renderer rend = Textmeshes[Num].GetComponent<Renderer> ();
if (rend.enabled)
rend.enabled = false;
else
rend.enabled = true;
}
if (Input.GetKeyDown(KeyCode.LeftArrow)) {
Num--;
Renderer rend = Textmeshes[Num].GetComponent<Renderer> ();
if (rend.enabled)
rend.enabled = false;
else
rend.enabled = true;
}
if (Num < 0) {
Num = 0;
Renderer rend = Textmeshes[Num].GetComponent<Renderer> ();
if (rend.enabled)
rend.enabled = false;
else
rend.enabled = true;
}
}
}
You shouldn't call GetComponent every time in Update but instead initialize all of them once e.g. in a dictionary
// Dictionary to store the references to the Renderer components
// Makes it easier to find them later
// You could as well only use a List instead and rely on the index
Dictionary<GameObject, Renderer> renderers = new Dictionary<GameObject, Renderer>();
private void Awake()
{
// Get all references already at app start
foreach(var textMesh in Textmeshes)
{
renderers[textMesh] = textMesh.GetComponent<Renderer>();
// And hide all from beginning
renderers[textMesh].enabled = false;
}
// Then enable only the first one depending what your Num is at start
SanatizeNum();
SetRendererEnabled(Num, true);
}
Then I would extract your code from Update into multiple methods to make it cleaner, remove redundancy and make it easier to maintain.
private void Update()
{
if (Input.GetKeyDown(KeyCode.RightArrow))
{
GoToNext();
}
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
GoToLast();
}
}
private void GoToNext()
{
// Hide the current renderer
SetRendererEnabled(Num, false);
Num++;
SanatizeNum();
// Show the new renderer
SetRendererEnabled(Num, true);
}
private void GoToLast()
{
SetRendererEnabled(Num, false);
Num--;
SanatizeNum();
SetRendererEnabled(Num, true);
}
private void SanatizeNum()
{
Num = Mathf.Clamp(Num, 0, Textmeshes.Count -1);
}
private void SetRendererEnabled(int index, bool enabled)
{
renderers[Textmeshes[index]].enabled = enabled;
}
I have tried to fix this for weeks now without succeeding and REALLY need help.
I am able to sync color on my non-player objects when i initiate the color sync on the actual player object.
However, the problem I have is that i want to attach a script to the non-player object that manage the color sync. I have the same for position and rotation sync that works.
When i drag the non-player object i color it red and when i drop the drag i re-color it back to white. However, i get problem with authorization when dragging on the remote client and that is why i think I am not able to solve.
Here is the sequence:
On-DragStart a) AssignNetworkAuthority b) Change Color to red
Drag
On_DragEnd a) Repaint to white b) ReleaseNetworkAuthority
When I do this from the remote-client i get an authority error. My conclusion, probably wrong, is that the assign-remove authority statements get out of sync with the painting for some reason.
Here is the code i use (childGameObject is assigned earlier):
public void On_DragStart (Gesture gesture) {
if (!isLocalPlayer)
return;
// Assign Network Authority
myNetID = childGameObject.GetComponent<NetworkIdentity> ();
// >>>> HERE I ASSIGN AUTHORITY
Cmd_AssignNetworkAuthority (myNetID.netId, childGameObject);
// >>>> HERE I CHANGE THE COLOR TO RED
childGameObject.GetComponent<Renderer> ().material.color = Color.red;
}
public void On_Drag(Gesture gesture) {
if (!hasAuthority)
return;
if (firstRun) {
firstRun = false;
}
myTransform.transform.position = Camera.main.ScreenToWorldPoint (new Vector3 (gesture.position.x, gesture.position.y, 1f));
}
public void On_DragEnd (Gesture gesture) {
if (!isLocalPlayer)
return;
// >>>> HERE I CHANGE THE COLOR BACK TO WHITE
// gesture.pickedObject.GetComponent<Renderer> ().material.color = Color.white;
childGameObject.GetComponent<Renderer> ().material.color = Color.white;
// >>>> HERE I RELEASE AUTHORITY
Cmd_ReleaseNetworkAuthority ();
}
Here is the actual sync.script that is attached to the non-player object:
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class Object_ColorSync : NetworkBehaviour {
[SyncVar] private Color syncColor;
public GameObject myGO;
void Start () {
syncColor = myGO.GetComponent<Renderer> ().material.color;
}
void Update () {
DoColor ();
}
void DoColor() {
Cmd_DoColor (myGO.GetComponent<Renderer> ().material.color);
}
[Command]
void Cmd_DoColor(Color _myColor) {
syncColor = _myColor;
Rpc_DoColor (_myColor);
}
[ClientRpc]
void Rpc_DoColor(Color _syncColor) {
myGO.GetComponent<Renderer> ().material.color = _syncColor;
}
}
The sync. script is for testing so not really optimized in regards when i fire off the Cmd_DoColor. In final i will add an if statement and check if color have been changed.
Problem solved :-) I was re-thinking my approach to this problem after i posted this thread and did a SyncVar hook instead and problem solved!
Here is the new sync-code:
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class Object_ColorSync : NetworkBehaviour {
[SyncVar (hook = "OnColorChange")] Color newColor;
private Color currentColor;
void OnColorChange(Color _myColor) {
gameObject.GetComponent<Renderer> ().material.color = _myColor;
currentColor = _myColor;
}
void Update () {
if (!hasAuthority)
return;
if (currentColor != gameObject.GetComponent<Renderer> ().material.color) {
Cmd_DoColor (gameObject.GetComponent<Renderer> ().material.color);
}
}
[Command]
void Cmd_DoColor(Color _theColor) {
newColor = _theColor;
}
}