In my Unity project I have an Empty GameObject which has 3 child GameObjects. The Empty GameObject is called Cable Strip and if the camera move to a special position, I want that the material of the three child Object start to change from white to yellow and back. Unfortunately all my solutions does not work.
This is what I have so far:
using UnityEngine
public class BlinkingObject : MonoBehaviou
{
public GameObject CableStrip;
public Material white, yellow;
public GameObject camManager; //Empty GameObject with the Main Camera
private Vector3 camPosition;
bool blinkObject;
void Update()
{
if(camManager.transform.position == camPosition)
{
InvokeRepeating("Blink", 0, 1);
}
}
public void Blink()
{
Renderer[] colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
foreach (var cableChild in colorCable)
{
if(blinkObject)
{
cableChild.material = yellow;
blinkObject = false;
} else
{
cableChild.material = white;
blinkObject = true;
}
}
}
}
How can I get my gameobject to blink in a nice way?
Edit
Sorry wrong definition of does not work. The problem is, that the color changes too fast. Even if I change the seconds by InvokeRepeating nothing changes. It still changes the color in milliseconds even if I write InvokeRepeating("Blink", 0, 10);.
Problem:
InvokeRepeating("Blink", 0, 1);
is called multiple times. So every frame the condition is fulfilled you add a new repeating invoke ... ending up with probably thousands of parallel invocations that are always repeating.
What you want to do instead is enabling the blink when the condition is fulfilled and disable it when not. I wouldn't use Invoke or InvokeReapting in this case. You can achieve it by simply using a timer:
private float timer;
private bool blinkObject;
private Renderer[] colorCable;
private void Awake()
{
// you want to do this only once!
colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
}
private void Update()
{
HandleBlink();
}
// As suggested by Cid you can ofcourse move it all to a method
// so you don't disturb other things happening in Update
private void HandleBlink()
{
// is the condition fulfilled?
var doBlink = camManager.transform.position == camPosition;
// if not do nothing
if(!doBlink) return;
// reduce the timer by time passed since last frame
timer -= Time.deltaTime;
// if timer not under 0 do nothing else
if(timer > 0) return;
// If timer exceeded invert the blinkObject flag
blinkObject = !blinkObject;
foreach (var cableChild in colorCable)
{
// Shorthand for if(blinkObject) { ... = yellow; } else { ... = white; }
cableChild.material = blinkObject ? yellow : white;
}
// and reset the timer
timer = 1f;
}
Alternatively the same thing could also be achieved by using a Coroutine which is often a bit cleaner (but that's opinions):
// flag for preventing concurrent routines
private bool isBlinking;
private Renderer[] colorCable;
private void Awake()
{
// you want to do this only once!
colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
}
private void Update()
{
// is the condition fulfilled?
if(camManager.transform.position == camPosition)
{
if(!isBlinking) StartCoroutine(BlinkRoutine());
}
else
{
StopAllCoroutines();
isBlinking = false;
}
}
private IEnumerator BlinkRoutine()
{
isBlinking = true;
var blinkObject = false;
// this looks scary but is fine in a Coroutine
// as long as you YIELD somewhere inside!
while(true)
{
blinkObject = !blinkObject;
foreach (var cableChild in colorCable)
{
// Shorthand for if(blinkObject) { ... = yellow; } else { ... = white; }
cableChild.material = blinkObject ? yellow : white;
}
yield return new WaitForSeconds(1);
}
}
Related
I want GameTipUI with a blank space for a certain amount of time and certain message(==NormalGuideTip) for a certain amount of time using CoRoutine.
I was thinking of putting an 'if' in the CoRoutine to check the time,so make it easy to empty the message, and display the message again.
However, the message just keeps getting blank.
What could be the problem?
I wanted to repeat the randomly selected game tip message for 5 seconds and the blank message for 7 seconds.
And I wanted to use realtimeForGameTip to immediately display a special message(==UrgentGameTip ) according to a specific game action later and repeat the above process again.
bool isGameTipOn= true;
public GameObject gameTipTitle;
public GameObject gameTipMsg;
Color normalTipColor = new Color(255f, 255f, 255f, 220f);
Color UrgentTipColor = Color.green;
float timeBetGameTip = 7f; //
float timeOfGameMsg = 5f; //
public readonly WaitForSeconds m_waitForSecondsForGameTip = new WaitForSeconds(7f);
float realtimeForGameTip= 0f;
public string[] NormalGuideTips;
public string[] UrgentGuideTips;
private void Start()
{
#region GameTipSetUp
OnOffGameTip(true);
StartCoroutine("GameTipUI");
#endregion GameTipSetUp
}
public void OnOffGameTip(bool OnOff)
{
isGameTipOn = OnOff;
gameTipTitle.gameObject.SetActive(isGameTipOn);
gameTipMsg.gameObject.SetActive(isGameTipOn);
}
private void ResetGameTip()
{
if (!isGameTipOn) return;
realtimeForGameTip = 0f ;
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = NormalGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = normalTipColor;
}
public void UrgentGameTip(string id)
{
if (!isGameTipOn) return;
=
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = UrgentGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = UrgentTipColor;
realtimeForGameTip = timeOfGameMsg;
}
public void MakeTermBetGameTip()
{
gameTipMsg.GetComponent<Text>().text = " blank ";
}
IEnumerator GameTipUI()
{
while (isGameTipOn)
{
if(realtimeForGameTip < timeOfGameMsg)
{
Debug.Log(realtimeForGameTip);
realtimeForGameTip += Time.deltaTime;
continue;
}
MakeTermBetGameTip();
yield return new WaitForSeconds(m_waitForSecondsForGameTip);
ResetGameTip();
}
}
Im fairly new to Unity and I am trying to make a Pause overlay for my game. Currently Resume does not resume the character movement half of the time. So the first time I pause the game and click resume the overlay is disabled but I will not be able to move, but if I press escape again and then Resume it works fine on the second try. One thing to note is that it works as intended if I press escape while in the pause menu and the logic is exactly the same. Here is my C# script:
public class PauseGame : MonoBehaviour
{
public GameObject Canvas;
bool pausedGame = false;
// Make sure pause screen is not active on game start
void Start()
{
Canvas.GetComponent<Canvas>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (pausedGame == false)
{
Time.timeScale = 0;
Canvas.GetComponent<Canvas>().enabled = true;
pausedGame = true;
}
else
{
Time.timeScale = 1;
Canvas.GetComponent<Canvas>().enabled = false;
pausedGame = false;
}
}
}
public void Resume()
{
Time.timeScale = 1;
Canvas.GetComponent<Canvas>().enabled = false;
pausedGame = false;
}
public void MainMenu()
{
SceneManager.LoadScene("MainMenu");
}
}
Let me know if I should add screenshots of my components.
Found the solution to this! For some reason the FirstPersonAIO controller did not get unpaused when I resumed the game. Where player is a public variable I added these two lines to my code and it fixed my problem:
controllerScript = player.GetComponent<FirstPersonAIO>();
controllerScript.ControllerPause();
I got a quick question for you. I have already everything prepared. I am making a simple launching game, where you need to kill all enemies (not time-based) to pass to the next level. I have 2 methods GoToNextLevel() and AllMonsterDied(); .
Need to make something like player have 3 attempts (3 launchings whenever he wants not based on time).
Then every launch checks if any monster left. If does just show 1 attemps less. After 3 times just restart scene.
Thanks a lot since I am new to both c# and unity that would mean the world to me.
public class LevelController: MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
}
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
}
Line from Monster.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (SouldDieFromCollision(collision))
{
tickSource.Play();
StartCoroutine(Die());
}
}
IEnumerator Die()
{
_hasDied =true;
GetComponent<SpriteRenderer>().sprite=_deadsprite;
_particleSystem.Play();
yield return new WaitForSeconds(1);
gameObject.SetActive(false);
}
Let's say you want the next level launch attempt happen when the player hits N key on the keyboard.
When the player hits the key, check if all monsters are dead. If so, call GoToNextLeve(), otherwise take off 1 attempt from the attempts available and restart the current scene.
int attempts;
void OnEnable()
{
attempts = 3;
}
void Update()
{
if (Input.GetKeyUp(KeyCode.H)
{
TryGoToNextLevel();
}
}
void TryGoToNextLevel()
{
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
else
{
attempts--;
}
if (attempts <= 0)
{
SceneManager.LoadScene(
SceneManager.GetActiveScene().name);
}
}
I'm trying to animate the button while it is being gazed on. I have got the following code in which I Raycast from a sphere to find the button that it hits.
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = screenPosition;
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
foreach (var result in results)
{
Debug.Log(result.gameObject.name);
}
In order to animate the button, I'm adding the following code to the button. Unfortunately, the OnPointerEnter or ``OnPointerExit is never being called.
[RequireComponent(typeof(Button))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public Image progressImage;
public bool isEntered = false;
RectTransform rt;
Button _button;
float timeElapsed;
Image cursor;
float GazeActivationTime = 5;
// Use this for initialization
void Awake()
{
_button = GetComponent<Button>();
rt = GetComponent<RectTransform>();
}
void Update()
{
if (isEntered)
{
timeElapsed += Time.deltaTime;
progressImage.fillAmount = Mathf.Clamp(timeElapsed / GazeActivationTime, 0, 1);
if (timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
_button.onClick.Invoke();
progressImage.fillAmount = 0;
isEntered = false;
}
}
else
{
timeElapsed = 0;
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("entered");
isEntered = true;
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("exit");
isEntered = false;
progressImage.fillAmount = 0;
}
#endregion
}
am I missing something ? or is there any other way of achieving this?
I guess your Raycast supposed to trigger the OnPointerEnter on all results.
You will need to use ExecuteEvents.Execute like e.g.
ExecuteEvents.Execute(result.gameObject, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
And will also at some point have to invoke pointerExitHandler.
I would therefore store a HashSet like
using System.Linq;
private HashSet<GameObject> previousEnters = new HashSet<GameObject>();
...
foreach (var result in results.Select(r => r.gameObject))
{
Debug.Log(result.name);
ExecuteEvents.Execute(result, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
// Store the item so you can later invoke exit on them
if(!previousEnters.Contains(result)) previousEnters.Add(result);
}
// This uses Linq in order to get only those entries from previousEnters that are not in the results
var exits = previousEnters.Except(results);
foreach(var item in exits)
{
if(item) ExecuteEvents.Execute(item, eventDataCurrentPosition, ExecuteEvents.pointerExitHandler);
}
You might actually want to implement your own custom PointerInputModule.
Alternative/Example
As a starter you could also use my answer to Using Raycast instead of Gaze Pointer from a while ago where I created a script based on Steam's VR Laserpointer which allows to interact with 3D objects and UI elements.
Note: Typed on smartphone but I hope the idea gets clear
I have a hint button on my game, but it fires up everytime you click on it. I need for it to fire only once every 3 clicks
i've tried adding a loadCount = 0 and an if statement
if (loadCount % 3 == 0) { }
but it dosen't seem to work
image for reference : https://ibb.co/L9LnNDw
Here is the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class HintScript : MonoBehaviour
{
LineRenderer line;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
line = GetComponent<LineRenderer>();
line.positionCount = transform.childCount;
for (int i = 0; i<transform.childCount; i++)
{
line.SetPosition(i, transform.GetChild(i).position);
}
}
// This is called from onClick of the Button
public void Hint()
{
FindObjectOfType<AdMobManager>().Hint = true;
FindObjectOfType<AdMobManager>().showInterstitial();
}
}
You should use a simply counter.
I also changed and commented other "issues" in your code:
public class HintScript : MonoBehaviour
{
// by adding SerializeField you can already set those references
// directly in the Unity Editor. This is better than getting them at runtime.
[SerializeField] private LineRenderer line;
[SerializeField] private AdMobManager adMobManager;
// you can still change the required clicks in the inspector
// Note: afterwards changing it here will have no effect!
[SerializeField] private int requiredClicks = 3;
// counter for your clicks
private int counter;
// Use this for initialization
void Start ()
{
// you should do this only once
line = GetComponent<LineRenderer>();
// you should also this only do once
adMobManager = FindObjectOfType<AdMobManager>();
// If instead you would drag those references directly into the now
// serialized fields you wouldn't have to get them on runtime at all.
}
// Update is called once per frame
void Update ()
{
line.positionCount = transform.childCount;
var positions = new Vector3[transform.childCount];
for (var i = 0; i < transform.childCount; i++)
{
position[i] = transform.GetChild(i).position;
}
// it is more efficient to call this only once
// see https://docs.unity3d.com/ScriptReference/LineRenderer.SetPositions.html
line.SetPositions(positions);
}
// This is called from onClick of the Button
public void Hint()
{
counter++;
if(counter < requiredClicks) return;
// Or using your solution should actually also work
if(counter % requiredClicks != 0) return;
adMobManager.Hint = true;
adMobManager.showInterstitial();
// reset the counter
counter = 0;
}
}
You can have a counter to keep track of how many times the player has clicked. Then fire when that counter is at 3.
int amountOfClicks = 0;
void clickme(){
amountOfClicks++;
if(amountOfClicks == 3){
amountOfClicks = 0;
YourFunction();
}
}
void YourFunction(){
// do something...
}
1.Keep a static/global variable.
2.You can't stop event notifications so, please add condition(if condition) upon code block inside the event handler code.