This is my script, when I click the debounce works at first, but after the wait you can just spam click and shoot many bullets at once. How can I fix this? I am a beginner so any help will be nice :) I had to get rid of some because stack overflow wasn't happy
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SingleShotGun : Gun
{
[SerializeField] Camera cam;
PhotonView PV;
public bool debounce = false;
public float debounce2;
public AudioSource audioSource;
public AudioClip audioClip;
private float timeStarted;
private float audioTime;
private void Start()
{
if (PV.IsMine)
{
audioSource.clip = audioClip;
float timeStarted = (float)PhotonNetwork.Time;
audioTime = 0;
}
}
private void Update()
{
if (PV.IsMine)
{
float audioTime = (float)PhotonNetwork.Time - timeStarted;
}
else
{
audioSource.time = audioTime;
}
}
void Awake()
{
PV = GetComponent<PhotonView>();
}
public override void Use()
{
if (debounce)
{
StartCoroutine(Deb());
return;
}
if (PV.IsMine)
{
audioSource.clip = audioClip;
float timeStarted = (float)PhotonNetwork.Time;
}
debounce = true;
Shoot();
}
private IEnumerator Deb()
{
Debug.Log("Debouncing");
yield return new WaitForSeconds(debounce2);
debounce = false;
}
}
I Tried to make a debounce script for unity3d, but it didn't work?
As a first note: in
float audioTime = (float)PhotonNetwork.Time - timeStarted;
you are creating a new local variable => the class field audioTime is not assigned in that case
same also for all occurrences of
float timeStarted = (float)PhotonNetwork.Time;
you want to remove the float in all three places in order to rather assign your class fields instead of over shadowing them with same named local variables, otherwise they will always have the default value 0.
Then within Use you are starting multiple concurrent routines that will all finish eventually and reset your debounce in unexpected moments. You probably rather want to wrap it in order to start only a single routine like
if(!debounce)
{
debounce = true;
Shoot();
StartCoroutine(Deb()):
}
Also again in general I would expect the entire Use should be wrapped in / start with
if (!PV.IsMine) return;
as it looks and sounds like none of those things should happen at all if this is not your gun ;)
And after setting
audioSource.clip = audioClip;
you also need to
audioSource.Play();
Related
I am writing a script for Enemy in my game, where they will attack hero using Coroutine at a certain interval. Though, while running the game, Enemy is not attacking. I have created two events for enemy animation specific for attack. The IE numerator part of code is not running. Can anyone tell what is going wrong?
I wrote Debug.Log("Hello"), to verify if it executes but it doesn't print.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour
{
[SerializeField] private float range = 3f;
[SerializeField] private float timeBetweenAttacks = 1f;
private Animator anim;
private GameObject player;
private bool playerInRange;
private BoxCollider[] weaponColliders;
// Start is called before the first frame update
void Start()
{
weaponColliders = GetComponentInChildren <BoxCollider[]> ();
player = GameManager.instance.Player;
anim = GetComponent <Animator> ();
StartCoroutine (attack());
}
// Update is called once per frame
void Update()
{
if(Vector3.Distance(transform.position,GameManager.instance.Player.transform.position) < range)
{
playerInRange = true;
}else{
playerInRange = false;
}
}
public IEnumerator attack()
{
Debug.Log("Hello");
if(playerInRange && !GameManager.instance.GameOver)
{
anim.Play("Attack");
yield return new WaitForSeconds(timeBetweenAttacks);
}
yield return null;
StartCoroutine(attack());
}
public void EnemyBeginAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = true;
}
}
public void EnemyEndAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = false;
}
}
}
The issue is likely the code weaponColliders = GetComponentInChildren<BoxCollider[]>();. GetComponentInChildren should only be called with component types (or interface types), but BoxCollider[] is an array type.
You should instead use GetComponentsInChildren<BoxCollider>();.
When Character is arrived destination, I want to get callback.
However I don't want to write into Update Function.
If I've get to write into Update, I want to write smartly and elegant code.
when we make game, if there is design pattern.
Let me teach about it.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Unit : MonoBehaviour
{
[SerializeField] Vector3 targetPosition;
private NavMeshAgent agent;
private bool arrived;
private Transform myPosition;
void Start()
{
myPosition = GetComponent<Transform>();
agent = GetComponent<NavMeshAgent>();
agent.updateRotation = false;
agent.updateUpAxis = false;
}
void Update()
{
agent.SetDestination(targetPosition);
}
public void MoveTo(Vector3 position, float stopDistance, Action onArrivedAtPosition)
{
targetPosition = position;
// here!
if (arrived)
{
onArrivedAtPosition();
}
}
private void IsArrived()
{
if (Vector3.Distance(myPosition.position, agent.destination) < 1.0f)
{
arrived = true;
}
arrived = false;
}
}
I would use a Coroutine. Coroutines are like small temporary Update routines but easier to control and maintain.
private Coroutine _currentRoutine;
private bool IsArrived()
{
// Instead of setting a field directly return the value
return Vector3.Distance(myPosition.position, agent.destination) < 1.0f;
}
public void MoveTo(Vector3 position, float stopDistance, Action onArrivedAtPosition)
{
// Here can/have to decide
// Either Option A
// do not allow a new move call if there is already one running
if(_currentRoutine != null) return;
// OR Option B
// interrupt the current routine and start a new one
if(_currentRoutine != null) StopCoroutine (_currentRoutine);
// Set the destination directly
agent.SetDestination(position);
// and start a new routine
_currentRoutine = StartCoroutine (WaitUntilArivedPosition(position, onArrivedAtPosition));
}
private IEnumerator WaitUntilArivedPosition (Vector3 position)
{
// yield return tells Unity "pause the routine here,
// render this frame and continue from here in the next frame"
// WaitWhile does what the name suggests
// waits until the given condition is true
yield return new WaitUntil (IsArrived);
_currentRoutine = null;
onArrivedAtPosition?.Invoke();
}
You could create a script like the one bellow and attach it to an empty gameobject, then place that empty gameobject at target position. Make sure your IsArrived method (in Unit script) is public and also assign the unit on the empty gameobjec's TargetPoint script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetPoint : MonoBehaviour
{
public float radius = 1f;
public Unit unit = null;
private bool called = false;
private void Start()
{
SphereCollider c = gameObject.AddComponent<SphereCollider>();
c.radius = radius;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawSphere(transform.position, radius);
}
private void OnTriggerEnter(Collider other)
{
if (called)
{
return;
}
if(other.transform == unit.transform)
{
unit.IsArrived();
called = true;
}
}
}
I have 1 script to PlayerMovement and one for powerUp I the power-up code I reference player movement to change the speed and change the bool named timer to true and I write that in log and when I touch the paper the speed doesn't change and the timer don't turn to true but in the log, its say that is yes
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private float TargetPos;
public float Speed;
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = new Vector2(TargetPos, transform.position.y);
}
public void right()
{
TargetPos = transform.position.x + Speed * Time.deltaTime;
}
public void left()
{
TargetPos = transform.position.x - Speed * Time.deltaTime;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Powrups : MonoBehaviour
{
public PlayerMovement pm;
public float PowerUpActiveTime;
public float StartPowerUpActiveTime;
public float peperSpeed;
float NormalSpeed;
bool timer;
bool timerover;
private void OnTriggerEnter2D(Collider2D col)
{
if (col.name == "peper")
{
pm.Speed = peperSpeed;
timer = true;
Debug.Log("timerOn");
Debug.Log(pm.Speed);
Debug.Log(timer);
}
}
private void Update()
{
while(timer)
{
GameObject Pause = GameObject.Find("Pause");
PauseScript pausescript = Pause.GetComponent<PauseScript>();
if (!pausescript.pause)
{
PowerUpActiveTime -= Time.deltaTime;
if(PowerUpActiveTime <= 0 )
{
timerover = true;
}
if (timerover)
{
timer = false;
}
}
}
if (timerover)
{
PowerUpActiveTime = StartPowerUpActiveTime;
pm.Speed = NormalSpeed;
}
}
private void Start()
{
PowerUpActiveTime = StartPowerUpActiveTime;
timerover = false;
NormalSpeed = pm.Speed;
}
}
Your mistake is that while loop.
You are lucky that until now you probably have tested this always while not being in pause mode ;)
This while would completely freeze your app and the entire Unity Editor!
In general be extremely careful with while loops and nested conditions like here, where the exit condition might never be fulfilled!
What happens currently is that you are not in pause mode so this while loop gets activated and runs until timer is set to false .. completely within one single frame. That is the reason why to you it seems that the value is never true.
What you rather want anyway is that code block be executed once per frame.
And in particular in a frame based application like Unity also have some performance impacts in mind.
You shouldn't use Find and GetComponent repeatedly within Update but store and re-use the results.
So your code should rather be
// If possible already drag this in via the Inspector
[SerializeField] private PauseScript _pauseScript;
private void Start()
{
PowerUpActiveTime = StartPowerUpActiveTime;
timerover = false;
NormalSpeed = pm.Speed;
// Get this ONCE as fallback on runtime
if(!_pauseScript)
{
_pauseScript = GameObject.Find("Pause"). GetComponent<PauseScript>();
// Or simply use
//_pauseScript = FindObjectOfType<_pauseScript>();
}
}
private void Update()
{
if(timer)
{
if (!_pauseScript.pause)
{
PowerUpActiveTime -= Time.deltaTime;
if(PowerUpActiveTime <= 0 )
{
timer = false:
PowerUpActiveTime = StartPowerUpActiveTime;
pm.Speed = NormalSpeed;
}
}
}
}
Besides all that, you should rather not let an external power-up control your player values. I would rather go the other way round and have your player object have a component which checks into which power-up items you run and react to it accordingly.
So your power-up itself would actually only be a trigger without any clue if or how exactly the player will be influenced by it.
So I put the object in the scene and then I made it "invisible" (deactivate if you will) from the inspector (the checkmark box next to the object's name) and after waiting 8 seconds it doesn't become visible. I am using Unity 2d and C#.
I have the game start paused for three seconds then plays after that which works. The first script is that one. The item is supposed to reappear after 8 seconds so after the game resumes, which doesn't work.
//delay before level starts script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class countDown : MonoBehaviour
{
public GameObject CountDown;
private void Start()
{
StartCoroutine("StartDelay");
}
void Update()
{
}
IEnumerator StartDelay()
{
Time.timeScale = 0;
float pauseTime = Time.realtimeSinceStartup + 3f;
while (Time.realtimeSinceStartup < pauseTime)
yield return 0;
CountDown.gameObject.SetActive(false);
Time.timeScale = 1;
}
{
//script for the flower to appear
IEnumerator Start()
{
print(Time.time);
yield return new WaitForSeconds(8);
print(Time.time);
flowerInScene.gameObject.SetActive(true);
}
[SerializeField] Transform flowerInScene;
}
I still don't really get your two methods called Start
You can simply call a StartCoroutine at the end of another Coroutine so you can chain them together (though there are surely better ways to do what you want in general):
using System.Collections;
using UnityEngine;
public class CountDown : MonoBehaviour
{
public GameObject CountDownObject;
public GameObject flowerObject;
private void Start()
{
StartCoroutine(Delay());
}
private IEnumerator Delay()
{
yield return new WaitForSeconds(3);
HideCountdown();
StartCoroutine(FlowerDelay());
}
private void HideCountdown()
{
CountDownObject.SetActive(false);
}
private IEnumerator FlowerDelay()
{
yield return new WaitForSeconds(8);
ShowFlower();
}
private void ShowFlower()
{
flowerObject.SetActive(true);
}
}
I personaly don't like Coroutines .. they are not so easy to debug sometimes. I would prefer doing something like this with simple timers (though in the first moment it does look worse). Advantage is I can now directly watch the timer count down in the inspector:
using UnityEngine;
public class SimpleCountDown : MonoBehaviour
{
[Header("The Objects")]
public GameObject CountDownObject;
public GameObject FlowerObject;
[Header("Settings")]
// Here you can adjust the delay times
public float StartOffset = 3;
public float FlowerOffset = 8;
[Header("Debug")]
public float startTimer;
public float flowerTimer;
public bool isStartDelay;
public bool isFlowerDelay;
private void Start()
{
startTimer = StartOffset;
flowerTimer = FlowerOffset;
isStartDelay = true;
}
private void Update()
{
if (!isStartDelay && !isFlowerDelay) return;
if (isStartDelay)
{
startTimer -= Time.deltaTime;
if (startTimer <= 0)
{
HideCountdown();
isStartDelay = false;
isFlowerDelay = true;
}
}
if (isFlowerDelay)
{
flowerTimer -= Time.deltaTime;
if (flowerTimer <= 0)
{
ShowFlower();
isFlowerDelay = false;
this.enabled = false;
}
}
}
private void HideCountdown()
{
CountDownObject.SetActive(false);
}
private void ShowFlower()
{
FlowerObject.SetActive(true);
}
}
I'm programming in C# on Unity. When ever I need to reset a variable in a certain interval, I would tend to declare a lot of variables and use the Update() function to do what I want. For example, here is my code for resetting a skill's cooldown (Shoot() is called whenever player presses shoot key):
using UnityEngine;
using System.Collections;
public class Player : MonoBehavior
{
private bool cooldown = false;
private float shootTimer = 0f;
private const float ShootInterval = 3f;
void Update()
{
if (cooldown && Time.TimeSinceLevelLoad - shootTimer > ShootInterval)
{
cooldown = false;
}
}
void Shoot()
{
if (!cooldown)
{
cooldown = true;
shootTimer = Time.TimeSinceLevelLoad;
//and shoot bullet...
}
}
}
Is there any better ways to do the same thing? I think my current code is extremely messy with bad readability.
Thanks a lot.
Use Invoke this will save you a lot of variables.
public class Player : MonoBehavior
{
private bool cooldown = false;
private const float ShootInterval = 3f;
void Shoot()
{
if (!cooldown)
{
cooldown = true;
//and shoot bullet...
Invoke("CoolDown", ShootInterval);
}
}
void CoolDown()
{
cooldown = false;
}
}
A way without Invoke that is a bit easier to control:
public class Player : MonoBehavior
{
private float cooldown = 0;
private const float ShootInterval = 3f;
void Shoot()
{
if(cooldown > 0)
return;
// shoot bullet
cooldown = ShootInterval;
}
void Update()
{
cooldown -= Time.deltaTime;
}
}