I’d like to show some text when the player kills e.g. 3, 4, or 5 enemies in a given time (in Unity3d).
I have a timer (comboTimer) which is in my PlayerControl-script that starts when an enemy gets shot by the player. When the timer starts a counter (comboCounter) gets increased everytime an enemy gets shot by the player. The other script (EnemyControl) controls the way how the appropriate text gets displayed when the kill-counter (comboCounter) reaches a certain value (e.g. 3 kills = “Combo x3”). This should be done within the timer’s value.
The problem is the text that gets shown is in almost most cases 3 kills (“Combo x3”). I never reach 4 or 5 kills within the timer’s value even when I increase the waitTime to e.g. 5 or 10 seconds. There is a bug within the logic but can’t find it.
This is the Timer of my PlayerControl-script:
public static bool comboTimerRunning = false;
public static int comboCounter;
float comboWaitTime = 1f;//1.0 Sekunden
float comboTimer;
public void ComboTimer() {
comboTimer += Time.deltaTime;
if (comboTimer > comboWaitTime)
{
ResetComboTimer();
}
}
public void ResetComboTimer () {
comboTimer = 0f;
comboTimerRunning = false;
comboCounter = 0;
}
This is the script that controls the handling of the displayed text (EnemyControl):
if (!PlayerControl.comboTimerRunning) {
PlayerControl.comboTimerRunning = true;
}
PlayerControl.comboCounter++;
if (PlayerControl.comboCounter >= 3) {
if (PlayerControl.comboCounter == 3) {
comboTxtObj.GetComponent<ComboTxt>().ShowComboText("Combo x3");
} else if (PlayerControl.comboCounter == 4) {
comboTxtObj.GetComponent<ComboTxt>().ShowComboText("Awesome");
} else if (PlayerControl.comboCounter >= 5) {
comboTxtObj.GetComponent<ComboTxt>().ShowComboText("Mega-Kill");
}
PlayerControl.comboTimerRunning = false;
}
Related
Let's say I have two scripts:
SpawnManager
Enemy
In SpawnManager, I have the function SpawnEnemyWave that should instantiate 3 enemies, if the random number generator is lower than 5, then one of them should have a higher movement speed, the other shouldn't move at all.
In SpawnManager:
bool toughEnemy = true;
int waveNumber = 3;
float randomNumber = Random.Range(0, 10);
void Start()
{
SpawnEnemyWave(waveNumber);
}
void SpawnEnemyWave(int enemiesToSpawn)
{
float randomNumber = Random.Range(0, 10);
print(randomNumber);
for (int i = 0; i < enemiesToSpawn; i++)
{
if ((randomNumber < 5) && toughEnemy)
{
print("Tough");
Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
toughEnemy = false; //I make sure there is only one tough enemy per wave
}
else
{
print("Weak");
Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
}
}
}
In Enemy, I'm checking if the toughEnemy variable is set to true to modify the enemy speed before the instantiation, I put those if statements in the start function because I think than when an enemy is instantiated is when it is called.
void Start()
{
spawnManager = GameObject.Find("Spawn Manager").GetComponent<SpawnManager>();
if (spawnManager.toughEnemy)
{
speed = 1;
print("Speed " + speed);
}
else
{
speed = 0;
print("Speed " + speed);
}
}
The problem is, when the random number is 0 in the logs i see this...
random number:0
Tough (the i in the for loop is 0)
Weak (the i in the for loop is 1)
Weak (the i in the for loop is 2)
speed 0
speed 0
speed 0
And what I was expecting was something like below, because I'm modifying the variable in the SpawnManager script first before instantiating the enemy.
random number:0
Tough (the i in the for loop is 0)
speed 1
Weak (the i in the for loop is 1)
speed 0
Weak (the i in the for loop is 2)
speed 0
What am I missing here?
Timing. You’re partly correct thinking that Start will be called when the object is instantiated. But, it will be called when the next frame starts. In your current loop, you’re setting up the objects to be instantiated, then you set the toughEnemy to true. When the next frame starts, all the enemies think that a tough enemy has been set, and the output you see is correct.
If you want the manager to control the enemies, I’d personally include something like a Setup method, called by the manager. For example, in the Enemy script:
public bool isSetup { get; private set; }
public bool isTough { get; private set; }
void Setup(bool tough)
{
if ( isSetup ) return;
isSetup = true;
isTough = tough;
speed = tough ? 1 : 0;
}
Then, when you instantiate the enemy in your manager, do something like:
for (int i=0; i<enemiesToSpawn; i++)
{
var enemy = Instantiate( enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation );
var e = enemy.GetComponent<Enemy> ( );
if ((randomNumber < 5) && toughEnemy)
{
print("Tough");
toughEnemy = false; //I make sure there is only one tough enemy per wave
e.Setup(true);
}
else
{
print("Weak");
e.Setup(false);
}
}
Here’s the life cycle of a ‘frame’
Notice that the Update method processing occurs about in the middle of the frame lifecycle. You’re Instantiating 3 objects in the same Update method. THEN … the frame ends, and at the beginning of the next frame, the Start event for each of the new objects is triggered.
I would do this by putting the toughEnemy flag in the Enemy script itself. I would then add a SetTough() method to Enemy, which I would call from SpawnManager via GetComponent() after the Enemy is instantiated.
In a small simulation game (A.I. spaceship shooter) that I am developing, I am trying to come up with an effective shield function or IEnumerator which can be called or started and do multiple things:
Count down the shield's cooldown if it is above zero
Activate the shield for the set duration (5 seconds) if the cooldown has ended
Deactivate the shield when the duration expires
However, I run into some problems when trying this using only an Ienumerator. I have been able to use IEnumerators to count down timers and cooldowns before but trying to do both a cooldown and duration doesn't seem to work as Unity does not let me WaitForSeconds twice without leaving the IEnumerator.
Similarly, each ship has a turret and inside of that turret is an IEnumerator which fires or counts down its cooldown, whichever is needed for the situation.
// Fire continuously if in range and we have more than 1 shot left
// Otherwise, reload for (rate) seconds and reset shots left
public IEnumerator Fire(Vector2 target) {
firing = true;
if (cooldown <= 0) {
if (bullets > 0) {
// Fire a bullet
bullets--;
// Instatiate the bullet
}
} else {
// Reload
cooldown = rate;
bullets = count;
}
} else {
yield return new WaitForSeconds(1);
cooldown--;
}
firing = false;
yield break;
}
The Fire Coroutine is called by using the firing flag to check whether it is running or not and if it is not call
var fire = turret.Fire(shootTarget + offset);
if (!turret.firing && InRange() == true) {
StartCoroutine(fire);
}
every second or so if the ship is alive and we have a target.
I do think that my current use of the IEnumerator is not recommended because it has to be called at least every second, but with how small the environment is at the moment, it doesn't appear to be an issue.
Any help is appreciated.
Quick code without coroutines, no idea if it works in game. Should give you some idea though, I hope.
public class Shield : MonoBehaviour {
public float duration;
public float cooldown;
public float lastActivated;
public bool IsActivated => (lastActivated - (Time.time - duration)) > 0;
public bool onCoolDown => (lastActivated - (Time.time - cooldown)) > 0;
public void Activate(){
if(onCoolDown) return;
lastActivated = Time.time;
}
void LateUpdate(){
if(IsActivated) //Show shield effects, blahblah
else //Do nothing, blahblah
}
}
This question already has answers here:
Coroutines and while loop
(3 answers)
Closed 6 years ago.
I wish to move an object over time using coroutines. I wanted to get an object from point A to point B over say 2 seconds. To achieve this I used the following code:
IEnumerator _MoveObjectBySpeed()
{
while (TheCoroutine_CanRun)
{
if(myObject.transform.position.y <= UpperBoundary.position.y)
{
myObject.transform.position = new Vector3(myObject.transform.position.x,
myObject.transform.position.y + Step, myObject.transform.position.z);
}
yield return new WaitForSeconds(_smoothness);
}
TheCoroutine_CanRun = true;
moveAlreadyStarted = false;
}
and the Step is calculated like
private void CalculateSpeed()
{
Step = _smoothness * allDistance / timeToReachTop;
}
where allDistance is the distance between the bottom and the top boundary.
_smoothness is a fix value. The thing is, the bigger I get this value, the more accurate the time gets to get from bottom to up. Note that a small value here means smoother movement. This smoothness is the time the coroutine waits in between moving the myObject.
The time is measured like this:
void FixedUpdate()
{
DEBUG_TIMER();
}
#region DEBUG TIME
public float timer = 0.0f;
bool allowed = false;
public void DEBUG_TIMER()
{
if (Input.GetButtonDown("Jump"))
{
StartTimer();
}
if (myObject.transform.position.y >= UpperBoundary.position.y)
{
StopTimer();
Debug.Log(timer.ToString());
//timer = 0.0f;
}
if (allowed)
{
timer += Time.fixedDeltaTime;
}
}
void StartTimer()
{
timer = 0;
allowed = true;
}
void StopTimer()
{
allowed = false;
}
#endregion
The results were:
When I wanted the object to reach the top under 1 second and set the _smoothness to 0.01, the time the myObject took to get to the top was 1.67 seconds. When _smoothness was 0.2s, the time to actually reach the top was 1.04s.
So why is this so inaccurate and how to make it work fine?
This smoothness is the time the coroutine waits in between moving the myObject
The mistake you're making is assuming that a co-routine waits the perfect time before executing. Rather, it probably executes on the next frame after the timeout has finished.
Assuming you want smooth motion, you want to move the object every frame (e.g. in Update or co-routine that uses 'yield return null').
Note: Each frame may take a different duration (consider 144fps vs 15fps), and you can discover this in Time.deltaTime . https://docs.unity3d.com/520/Documentation/ScriptReference/Time-deltaTime.html
Currently I'm simply trying to change the sprites candle from unlit to lit when the player has 'picked up' both the candle and the matches and the candle will 'go out' after a certain amount of time. However, when the space bar is pressed the transition from unlit to lit isn't occurring, even though the debug log is returning true when it should. I'm posting here to get some guidance as I have spent most of the day looking online and literally have no idea how to proceed.
Basically the images I am trying to transition between are two different images which are in the sprites folder under assets.
This is what I've got so far.
//the two sprites transition
public Sprite unlitCandle;
public Sprite litCandle;
private SpriteRenderer spriteRenderer;
bool pickUpMatches = false;
bool pickUpCandle = false;
float timeRemaining =5;
bool candleLit = false;
// Use this for initialization
void Start () {
spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer.sprite == null)
spriteRenderer.sprite = unlitCandle;
}
// Update is called once per frame
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.CompareTag("Matches"))
{
collision.gameObject.SetActive(false);
pickUpMatches = true;
}
if (collision.gameObject.CompareTag("UnlitCandle"))
{
collision.gameObject.SetActive(true);
pickUpCandle = true;
}
}
public void CandleTimer()
{
if (candleLit == true)
{
timeRemaining = 5;
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0)
{
candleLit = false;
spriteRenderer.sprite = unlitCandle;
}
}
}
public void ChangeSprite()
{
if (spriteRenderer.sprite == unlitCandle)
{
spriteRenderer.sprite = litCandle;
}
}
void Update () {
if (pickUpCandle == true && pickUpMatches == true)
{
//Debug.Log(candleLit);
if (Input.GetKey(KeyCode.Space) && !candleLit)
{
CandleTimer();
ChangeSprite();
Debug.Log(timeRemaining);
candleLit = true;
//Debug.Log(candleLit);
}
}
}
}
Try comparing with a method like equals() instead of == in
spriteRenderer.sprite == unlitCandle
Because right now you are just comparing references and not the objects.
At least I think thats the problem.
There are a few possible issues with your code. First, you are calling changeSprite at the top of Update, which means that it is unconditionally being called every frame. Therefore, after a single frame of your candle being unlit, it will immediately change its sprite to litCandle.
I assume that the reason you are calling changeSprite every frame is in order to process the timer if you have a lit candle already. Really, you should move the code to process the timer (your whole second if statement in changeSprite) to a separate function and name it something like processCandleTimer. Call that at the top of Update and save the changeSprite method to only be called on the keypress.
Lastly, the issue that I suspect is giving you the most trouble is that you aren't resetting your timer, timeRemaining. The first time you light the candle the timer will go down to 0 after the 5 seconds pass. Every time changeSprite is run after that, you will change the sprite to litCandle in the first if statement and then immediately change it back to unlitCandle because the timer is 0 in the second. To remedy this, you need to add a line like timeRemaining = 5.0f; when the key is hit.
I have the a script called Timer.cs. This script is connected to some GUI Text, which displays the amount of time remaining in the game.
Also attached to this script is an Audio Source with my desired sound selected. When the clock reaches zero, the text changes to say "GAME OVER!" and the character controls lock up; however, the sound does not play.
All other instances of audio.Play() in my scene are working fine, and when I set the Audio Source to "Play On Awake", it plays without a problem. What could be the problem?
Using UnityEngine;
using System.Collections;
public class Timer : MonoBehaviour {
public float timer = 300; // set duration time in seconds in the Inspector
public static int sound = 1;
public static int go = 1;
bool isFinishedLevel = false; // while this is false, timer counts down
void Start(){
PlayerController.speed = 8;
PlayerController.jumpHeight = 12;
}
void Update (){
if (!isFinishedLevel) // has the level been completed
{
timer -= Time.deltaTime; // I need timer which from a particular time goes to zero
}
if (timer > 0)
{
guiText.text = timer.ToString();
}
else
{
guiText.text = "GAME OVER!"; // when it goes to the end-0,game ends (shows time text over...)
audio.Play();
int getspeed = PlayerController.speed;
PlayerController.speed = 0;
int getjumpHeight = PlayerController.jumpHeight;
PlayerController.jumpHeight = 0;
}
if (Input.GetKeyDown("r")) // And then i can restart game: pressing restart.
{
Application.LoadLevel(Application.loadedLevel); // reload the same level
}
}
}
Given that you are calling it as part of your Update routine, I'd have to guess that the problem is you calling it repeatedly. I.e. you're calling it every frame as long as timer <= 0.
You shouldn't call Play() more than once. Or at least not again while it is playing. A simple fix would be something along the lines of
if(!audio.isPlaying)
{
audio.Play();
}
See if that solves your problem, and then you can take it from there.
I had error using audio.Play(); and used following it fixed the error for me
GetComponent<AudioSource>().Play();