Since C# is being pretty cringe, I am asking for help on how to debug it based on the comments in the code. As a summary, I have a timer running in console (noob here, code copy-pasted or tutorial'd) and the numbers ONE and GO are running simultaneously, and I have not found a way to stop the timer after the intended result is reached, and help would be appreciated!
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public int startTime = 4;
void Start()
{
InvokeRepeating("timer", 1.0f, 1.0f);
}
void timer()
{
if (startTime > 0)
{
if (startTime == 3)
{
Debug.Log("THREE");
startTime -= 1;
}
else
{
if (startTime == 2)
{
Debug.Log("TWO");
startTime -= 1;
}
else
{
Debug.Log("ONE");
startTime -= 1;
//Precent "ONE" and "GO!" from running simultaneously
}
}
}
if (startTime == 0)
{
Debug.Log("GO!");
//Fix "GO!" playing to console indefinetly after timer is finished
}
void Update()
{
}
}
}
To do this, you can use the CancelInvoke method by passing the method name to quotes as a parameter you can call it when startTime goes to 0. You can safly remove the Update Method
public int startTime = 3;
void Start()
{
InvokeRepeating("timer", 1.0f, 1.0f);
}
void timer()
{
//go throw all the cases
switch (startTime)
{
case 0:
Debug.Log("GO!");
CancelInvoke("timer");
break;
case 1:
Debug.Log("ONE");
break;
case 2:
Debug.Log("TWO");
break;
case 3:
Debug.Log("THREE");
break;
}
//substract - from time
startTime -= 1;
}
Or use this Coroutine
void Start()
{
StartCoroutine("timer");
}
IEnumerator timer()
{
//debug the time
Debug.Log(startTime); //you can use the if statement do show it as text or use any humanizer **C# 101 series**
startTime--; //here we substract 1 from time
//we wait for 1s
yield return new WaitForSeconds(1);
//if the time still >= 0 repeat
if(startTime >=0)
StartCoroutine("timer");
}
i think you want
//if (startTime == 0)
else
{
Debug.Log("GO!");
//Fix "GO!" playing to console indefinetly after timer is finished
}
Related
I am making a badminton simulator in unity, where the opponent is a set of video clips. I am trying to add some delay to my update method so theres some time between two clips of the opponent. However this delay only applies to the video clips and not the shuttle that arrives from behind the video.
My Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class Video_Player : MonoBehaviour
{
public VideoPlayer activeCam, otherCam;
public List<VideoClip> playlist = new List<VideoClip>();
public GameObject shuttle;
VideoClip nextClip;
private bool Timer;
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
Timer=false;
// setup an event to automatically call SwitchCams() when we finish playing
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
}
void Update()
{
if (playlist.Count == 0)
return;
if(!Timer)
{
StartCoroutine(CountDown(5));
if (nextClip == null && activeCam.time >= activeCam.clip.length - 0.1)
{
PrepareNextPlaylistClip();
shuttle.SetActive(false);
}
if(activeCam.time >= 1.0f && activeCam.time <= 2.95f)
{
Debug.Log("start:"+activeCam.time);
shuttle.SetActive(true);
}
else
//if(activeCam.time >= 2.95f || activeCam.time <= 1.0f)
{
Debug.Log("end:"+activeCam.time);
shuttle.SetActive(false);
}
}
}
void SwitchCams(VideoPlayer thisCam)
{
activeCam = otherCam;
otherCam = thisCam;
activeCam.targetCameraAlpha = 1f;
otherCam.targetCameraAlpha = 0f;
Debug.Log("new clip: " + nextClip.name);
nextClip = null;
}
void PrepareNextPlaylistClip()
{
nextClip = playlist[0];
otherCam.clip = nextClip;
otherCam.Play();
playlist.RemoveAt(0);
}
//delay couroutine
IEnumerator CountDown(float delay)
{
Timer = true;
yield return new WaitForSeconds(delay);
Timer= false;
}
// randomize the video playlist
public static void Shuffle<T>(IList<T> playlist)
{
int n = playlist.Count;
while (n > 1)
{
n--;
int k = Random.Range(0, n);
T value = playlist[k];
playlist[k] = playlist[n];
playlist[n] = value;
}
}
}
Forgive me if I'm misunderstanding your code but rather than having it all in Update() couldn't you just have it in an IEnumerator like this?
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
//Run the function on start
StartCoroutine(Function());
}
IEnumerator Function()
{
while(true)
{
if(playlist.Count == 0)
{
//If you have no clips left exit out of the loop
break;
}
if(nextClip == null)
{
//If you have clips left load the next clip
shuttle.SetActive(false);
PrepareNextPlaylistClip();
}
yield return new WaitForSeconds(1); //This is your delay
//Execute the code you want to run after the delay here
}
}
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);
}
}
Good evening guys!
I've got a little problem. I am creating a Pacman-ish game, and I have created a timer which pops up and appears on the top corner of the screen which presents the current time passed since the first scene started.
I am struggling with making the timer stop at my Game-over Scene
Do you have any tips for solutions that might work?
I am very new to C# and Unity, so please have that in mind, and sorry for any inconvenience!
Thank you so much in advance!
(Oh, and btw, I have made another script for the timer to continue through scenes, and to not replicate itself when the game restarts, so that part is ok for now)
public Text timerText;
private float startTime;
private bool finnished = false;
// Start is called before the first frame update
void Start()
{
startTime = Time.time;
}
// Update is called once per frame
void Update()
{
if (finnished)
return;
float t = Time.time - startTime;
string minutes = ((int)t / 60).ToString();
string seconds = (t % 60).ToString("f2");
timerText.text = minutes + ":" + seconds;
}
public void Finnish()
{
finnished = true;
timerText.color = Color.yellow;
}
You could try an event:
Subscribe to the stopEvent event delegate:
public Text timerText;
private float startTime;
private bool finnished = false;
GameManager.stopEvent += Finnish;
// Start is called before the first frame update
void Start()
{
startTime = Time.time;
}
// Update is called once per frame
void Update()
{
if (!finnished)
{
float t = Time.time - startTime;
string minutes = ((int)t / 60).ToString();
string seconds = (t % 60).ToString("f2");
timerText.text = minutes + ":" + seconds;
}
}
public void Finnish()
{
finnished = true;
timerText.color = Color.yellow;
}
Inside a script (let's say, GameManager.cs or something), you create the event and delegate:
public delegate void EventHandler();
public static event EventHandler stopEvent;
Your Scene Loading Code (For example, could be called from a loading code in GameManager.cs)
//// scene is done
stopEvent();
You can store the timer result in singleton, perhaps a GameManager.cs file that is a singleton, which lives across scenes.
Ok Kale_Surfer_Dude, so I have sort of figured out how to trigger Finnish(); upon entering either of my two finish/goal scenes, but somehow, it doesn't trigger when i die or finish.
Do you see why?
public Text timerText;
private float startTime;
private bool finnished = false;
// Start is called before the first frame update
void Start()
{
startTime = Time.time;
// Creating temporary reference to current scene
Scene currentScene = SceneManager.GetActiveScene();
// Retrieve the name of this scene.
string sceneName = currentScene.name;
if(sceneName == "Goal" || sceneName == "MenuOnCollision")
{
Finnish();
}
// Retrieve the index of the scene in the project's build setting
int buildIndex = currentScene.buildIndex;
// Check the scene name as a conditional
switch (buildIndex)
{
case 4:
Finnish();
break;
case 6:
Finnish();
break;
}
}
// Update is called once per frame
void Update()
{
if (!finnished) {
float t = Time.time - startTime;
string minutes = ((int)t / 60).ToString();
string seconds = (t % 60).ToString("f2");
timerText.text = minutes + ":" + seconds;
}
}
public void Finnish()
{
finnished = true;
timerText.color = Color.yellow;
}
The following coroutine, when run on the press of a UI button, will run normally and show "3...2...1...0" and then disappear. However, when run as a part of the event handler delegate HandleRewardBasedVideoClosed (part of AdMob) it will show "3...1..." and then disappear. For the life of me I cannot figure out why it would act differently when called in these two different manners.
public IEnumerator Countdown() {
timeLeft = 3;
while (timeLeft >= 0) {
countdownText.text = timeLeft.ToString();
yield return new WaitForSecondsRealtime(1.0f);
timeLeft--;
}
if (timeLeft < 0) {
countdownText.gameObject.SetActive (false);
}
}
public void HandleRewardBasedVideoClosed(object sender, EventArgs args){
MonoBehaviour.print("HandleRewardBasedVideoClosed event received");
if (reward) {
StartCoroutine ( gm.Countdown ());
} else
SceneManager.LoadScene (SceneManager.GetActiveScene ().name);
}
I have a suspicion about this one. Is it that the count down is in fact doing "3.2 .. 2.1 .. 0.SetActive(false)" so quickly that you're not seeing it running the coroutine twice? If so the following code will resolve that particular problem (if that's the case):
private bool isCountingDown = false;
public IEnumerator Countdown()
{
if ( isCountingDown ) return;
isCountingDown = true;
for ( int timeLeft = 3; timeLeft >= 0; timeLeft-- )
{
countdownText.text = timeLeft.ToString();
yield return new WaitForSecondsRealtime(1.0f);
}
countdownText.gameObject.SetActive (false);
isCountingDown = false;
}
public void HandleRewardBasedVideoClosed(object sender, EventArgs args)
{
MonoBehaviour.print("HandleRewardBasedVideoClosed event received");
if (reward) {
// This might be a redundant check, as we're doing this in the
// Coroutine, but we may as well not go through the process of
// calling it in the first place.
if (! isCountingDown ) StartCoroutine ( gm.Countdown ());
} else
SceneManager.LoadScene (SceneManager.GetActiveScene ().name);
}
I have made a little Blackjack game, and I'd like to make the computer wait between each card he pulls, however using System.Threading.Thread.Sleep(int x) does not make the program wait between cards, but makes it wait for x * amount of cards..
I also know that using Thread.Sleep is not a good way, so I'd rather learn a better way as I am creating this program entirely for educative purposes.
I'll add the code underneath which decides whether or not a card should be drawn, and the method that draws the card.
private void ComputerTurn()
{
drawCard.Enabled = false;
finishTurn.Enabled = false;
while (computerTotalScore <= 11)
{
ComputerDrawCard();
}
drawAgain = true;
while (drawAgain)
{
ComputerDrawCard();
if (totalScore <= 21)
{
if (computerTotalScore > totalScore)
{
drawAgain = false;
}
else
{
drawAgain = true;
}
}
else
{
if (computerTotalScore > 16)
{
drawAgain = false;
}
else
{
drawAgain = true;
}
}
}
DecideWinner();
}
public void ComputerDrawCard()
{
cardAlreadyPulled = true;
while (cardAlreadyPulled)
{
cardType = random.Next(0, 4);
cardNumber = random.Next(0, 13);
if (!pulledCards[cardType, cardNumber])
{
cardAlreadyPulled = false;
pulledCards[cardType, cardNumber] = true;
}
}
ComputerUpdateCardPictures();
computerScore = cardScores[cardNumber];
if (computerScore == 1)
{
if (computerTotalScore <= 10)
{
computerScore = 11;
}
else
{
computerScore = 1;
}
}
computerTotalScore += computerScore;
txtComputerCurrentScore.Text = computerScore.ToString();
txtComputerTotalScore.Text = computerTotalScore.ToString();
System.Threading.Thread.Sleep(random.Next(250, 750));
}
There are multiple ways to achieve something like this. I believe what you're attempting to do is simulate a human taking time to do things. I recommend using a combination of expected wait times and a timer to achieve what you want.
class Example
{
public Example()
{
// create a stalled timer
_pulse = new Timer(this.TakeAction);
}
TimeSpan _drawdelay = TimeSpan.FromSeconds(2);
DateTime _lastAction = DateTime.MinValue;
Timer _pulse;
public void Start()
{
// start the timer by asking it to call the worker method ever 0.5 seconds
_pulse.Change(0, 500);
}
public void Stop()
{
// stop the timer by setting the next pulse to infinitely in the future
_pulse.Change(Timeout.Infinite, Timeout.Infinite);
}
void TakeAction(object x)
{
lock (_pulse)
{
DateTime now = DateTime.Now;
if(now - _lastAction > _drawdelay)
{
// do work ...
_lastAction = now;
}
}
}
}
That said, the above code will run into issues if the work being done takes longer than 500 milliseconds to complete. Add thread safety as necessary.
I would add a last time drawn and time between draws members. Then before drawing a card, get the time between now and the last time pulled. If the time is greater than the allowed time between the draws its cool to draw.
private DateTime _lastWrite = DateTime.Now;
private TimeSpan _delay = TimeSpan.FromMilliseconds(100);
public void ComputerDrawCard() {
var now = DateTime.Now;
if (now - _lastWrite < _delay)
return;
_lastWrite = now;
draw card...
}
Here's a gist of an example working correctly.