why does the cooldown on my gun not work? - unity3d

I have this gun that's supposed to have a cooldown after every shot by using time between shots += Time.deltaTime. The problem is that it's like timeBeetweenShots dosen't increase. Here's the part of my code that I think matters:
private void Update()
{
timeSinceLastShot += Time.deltaTime;
}
public bool CanShoot() => !gunData.reloading && timeSinceLastShot > 1 / (gunData.fireRate / 60);
public void Shoot()
{
if (gunData.currentAmmo > 0)
{
if (CanShoot())
{
Debug.Log("shooting");
gunData.currentAmmo--;
timeSinceLastShot = 0;
OnGunShot();
}
} else
{
StartCoroutine(Reload());
}
}
what did I do wrong?

I think we might need to use Time.time.
The doc link is just perfect for gun fire interval.
However I had edit your code.
using System.Collections;
using UnityEngine;
public class Gun : MonoBehaviour
{
GunData gunData; // gundata might be access from player
float timeSinceLastShot = 0;
private void Update()
{
}
public bool CanShoot() => !gunData.reloading && (timeSinceLastShot + gunData.fireRate) < Time.time;
public void Shoot()
{
if (gunData.currentAmmo > 0)
{
if (CanShoot())
{
Debug.Log("shooting");
gunData.currentAmmo--;
timeSinceLastShot = Time.time;// save the last time that shot
OnGunShot();
}
}
else
{
StartCoroutine(Reload());
}
}
public void OnGunShot()
{ // animation might be here
}
IEnumerator Reload()
{ // relaoding might be here
yield return new WaitForEndOfFrame();
}
}
public class GunData // just dummy data
{
public int currentAmmo = 10;
public float fireRate = 0.2f;
public bool reloading = false;
}

Related

What is wrong with this code? I tried to make ads show every 5 rounds but it doesn't work

I have this script where I want the ads to show every 5 rounds/losses. When I test the game the ads just don't show. They only show when I put 1 in the gamesToShowAd variable. I have tried multiple ways how to make ads play every 5 rounds/losses and none of them worked.
GameManager script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public GameObject gameOverCanvas;
public AdsManager ads;
int gameCount = 0;
int gamesToShowAd = 5;
private void Start()
{
Time.timeScale = 1;
ads.ShowBanner();
gameCount = 0;
}
public void GameOver()
{
gameOverCanvas.SetActive(true);
Time.timeScale = 0;
gameCount++; //increment game count
if (gameCount >= gamesToShowAd) // check if player played enough games to show ad
{
ads.PlayAd();
gameCount = 0; // reset counter
}
}
public void Replay()
{
SceneManager.LoadScene(0);
}
}
Maybe its something to do with the ads manager script?
AdsManager:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Advertisements;
public class AdsManager : MonoBehaviour, IUnityAdsListener
{
#if UNITY_IOS
string gameId = "#######";
#else
string gameId = "#######";
#endif
Action onRewardedAdSuccess;
// Start is called before the first frame update
void Start()
{
Advertisement.Initialize(gameId);
Advertisement.AddListener(this);
ShowBanner();
}
public void PlayAd()
{
if(Advertisement.IsReady("Interstitial_Android"))
Advertisement.Show("Interstitial_Android");
}
public void PlayRewardedAd(Action onSuccess)
{
onRewardedAdSuccess = onSuccess;
if(Advertisement.IsReady("Rewarded_Android"))
{
Advertisement.Show("Rewarded_Android");
}
else
{
Debug.Log("Rewarded ad is not ready!");
}
}
public void ShowBanner()
{
if (Advertisement.IsReady("Banner_Android"))
{
Advertisement.Banner.SetPosition(BannerPosition.BOTTOM_CENTER);
Advertisement.Banner.Show("Banner_Android");
}
else
{
StartCoroutine(RepeatShowBanner());
}
}
public void HideBanner()
{
Advertisement.Banner.Hide();
}
IEnumerator RepeatShowBanner()
{
yield return new WaitForSeconds(1);
ShowBanner();
}
public void OnUnityAdsReady(string placementId)
{
Debug.Log("ADS ARE READY!");
}
public void OnUnityAdsDidError(string message)
{
Debug.Log("ERROR: " + message);
}
public void OnUnityAdsDidStart(string placementId)
{
Debug.Log("VIDEO STARTED!");
}
public void OnUnityAdsDidFinish(string placementId, ShowResult showResult)
{
if (placementId == "Rewarded_Android" && showResult == ShowResult.Finished)
{
onRewardedAdSuccess.Invoke();
}
}
}
Any help is appreciated!
Instead of 5 rounds, why don't you make it 20% chance of playing every round? This is more efficient than your way.
But your issue seems to be here. (Unity calls Start() and Awake() every time the scene that the object is from is called)
Take a look at this.
private void Start(){
Time.timeScale = 1;
ads.ShowBanner();
gameCount = 0;
//It sets to 0 every time your scene reloads
//Try creating this object at your main menu or loading a empty scene before hitting play on menu
}
Or if you want that percentage to show ads, create this:
[Range(0, 10)]
public int chanceToShowAds;
private int currentChance;
public void GameOver(){
currentChance = Random.Range(0, 10);
if (currentChance >= chanceToShowAds)
ads.PlayAd();
}

Unity: spawning enemies works but they are not visible

I'm busy with creating a school project where I need to integrate wave spawners.
For now, I used this video as an example and implemented it in my project.https://www.youtube.com/watch?v=q0SBfDFn2Bs
Everything is spawning fine and I also get my debug logs. The main problem is: I can't see my enemies. (not John Cena guys hahah, in that case it would be logic ;) )
I'm new to Unity and I like the interface and workflow but I have a lot to learn. I hope someone can help me out! :)
Source WaveSpawner.cs:
using UnityEngine;
using System.Collections;
public class WaveSpawner : MonoBehaviour
{
public enum SpawnState { SPAWNING, WAITING, COUNTING };
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
public Wave[] waves;
private int nextWave = 0;
public int NextWave
{
get { return nextWave + 1; }
}
public Transform[] spawnPoints;
public float timeBetweenWaves = 5f;
private float waveCountdown;
public float WaveCountdown
{
get { return waveCountdown; }
}
private float searchCountdown = 1f;
private SpawnState state = SpawnState.COUNTING;
public SpawnState State
{
get { return state; }
}
void Start()
{
if (spawnPoints.Length == 0)
{
Debug.LogError("No spawn points referenced.");
}
waveCountdown = timeBetweenWaves;
}
void Update()
{
if (state == SpawnState.WAITING)
{
if (!EnemyIsAlive())
{
WaveCompleted();
}
else
{
return;
}
}
if (waveCountdown <= 0)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine( SpawnWave ( waves[nextWave] ) );
}
}
else
{
waveCountdown -= Time.deltaTime;
}
}
void WaveCompleted()
{
Debug.Log("Wave Completed!");
state = SpawnState.COUNTING;
waveCountdown = timeBetweenWaves;
if (nextWave + 1 > waves.Length - 1)
{
nextWave = 0;
Debug.Log("ALL WAVES COMPLETE! Looping...");
}
else
{
nextWave++;
}
}
bool EnemyIsAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0f)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectWithTag("Enemy") == null)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
for (int i = 0; i < _wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform _sp = spawnPoints[ Random.Range (0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
}
From what I can see is you're also changing the Z position of the bat. Generally you would not want to do that in a 2D game cause it can go behind stuff as is happening there. In the code I did notice that you are using random points in the world so you would want to check the Z position of those points and change them to 0 or 1.Here's the Image of what you need to change on the points! Hope this helps!

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

Lookat targeted enemy and orbit

Previously I had a script that I could look at a player but only when I had the Tab buttoned pressed down but could not cycle through enemies or anything. Looking all day for answers I came by this and decided it would be great I can now find things tagged enemies and cycle though them but I have not been able to figure out a way to lookat the enemy and stay looking at them and be able to move around them in a circle.
public class debug : MonoBehaviour
{
public List<Transform> targets;
public Transform selectedTarget;
public string targetTag = "Enemy";
private Transform myTransform;
//Use this for initialization
void Start()
{
targets = new List<Transform>();
selectedTarget = null;
myTransform = transform;
AddAllEnemies();
}
public void AddAllEnemies()
{
GameObject[] go = GameObject.FindGameObjectsWithTag(targetTag);
foreach (GameObject enemy in go)
{
AddTarget(enemy.transform);
}
}
public void AddTarget(Transform enemy)
{
targets.Add(enemy);
}
/*private void SortTargetsByDistance()
{
targets.Sort(delegate (Transform t1, Transform t2) {
return (Vector3.Distance(t1.position, myTransform.position).CompareTo)
(Vector3.Distance(t2.position, myTransform.position));
});
} */
private void SortTargetsByDistance()
{
targets.RemoveAll(target => target == null);
targets.Sort(delegate (Transform t1, Transform t2) {
return (Vector3.Distance(t1.position, myTransform.position).CompareTo)
(Vector3.Distance(t2.position, myTransform.position));
});
}
private void TargetEnemy()
{
if (selectedTarget == null)
{
SortTargetsByDistance();
selectedTarget = targets[0];
}
else
{
int index = targets.IndexOf(selectedTarget);
if (index < targets.Count - 1)
{
index++;
}
else
{
index = 0;
}
selectedTarget = targets[index];
}
}
//Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
TargetEnemy();
}
}
}
I am trying to use transform.LookAt(TargetEnemy); in void update but I get an error saying cannot convert from method group to transform. Im really stuck on this now any help is appreciated.
Edit: I am a big dummy I used transform.LookAt(selectedTarget); instead and the character for a split second will turn and lookat the enemy. Now i need to keep the player looking at the target and move around them.
Transform.LookAt
You need to pass it a Transform, not void.
TargetEnemy();
transform.LookAt(selectedTarget);
Alternatively, you can make TargetEnemy() return a Transform.:
private Transform TargetEnemy()
{
/* stuff */
return selectedTarget;
}
transform.LookAt(TargetEnemy());
Hello thanks to everyone that looked and thank #SonicBlue22 for your answer I figured it out after a couple more hours.
void Update()
{
if (Input.GetKeyUp(KeyCode.Tab))
{
TargetEnemy();
transform.LookAt(selectedTarget);
}
if (selectedTarget != null)
{
transform.LookAt(selectedTarget);
}
if (Input.GetKeyUp(KeyCode.Q))
{
selectedTarget = null;
return;
}
}
}

pass int value from one script to another script in unity

I'm trying to pass a public int score value from one script to another script but it is giving me the error an object reference is required to access non-static member , here it is what I have done
public class firearrow : MonoBehaviour {
public GameObject Arrow;
public GameObject apple;
public int score = 0;
// Use this for initialization
void Start () {
this.gameObject.GetComponent<Rigidbody2D> ().AddForce (transform.right*1500.0f);
}
// Update is called once per frame
void Update () {
Vector3 diff = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
diff.Normalize();
float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 0);
if (Input.GetMouseButtonUp (0)) {
GameObject bullet_new;
bullet_new = Instantiate (Arrow,new Vector2 (-0.23f, -3.78f), Quaternion.identity) as GameObject;
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition),Vector2.zero);
if (hit.collider!= null ) {
LeanTween.move(bullet_new, hit.collider.transform.localPosition, 1);
if(hit.collider.tag == "fruit")
{
score++;
Destroy(hit.collider.gameObject,1);
Destroy(bullet_new,1);
}
}
}
}
}
the class I want to access the score
public class tick : MonoBehaviour {
public Text wintext;
// Use this for initialization
void Start () {
wintext.text = "";
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonUp (0)) {
if(firearrow.score == 3)
{
wintext.text="You Win";
}
}
}
}
Any suggestions?
Change line
public int score = 0;
to
public static int score = 0;
Note that you must only have one single instance of class firearrow, otherwise you might run into concurrency issues.