i have a gameobject that goes up if it stays in collision for 5 Secs, the problem is it only work once , i tried calling the OnGUI in the update when ever the ToggleGUI = true but did't work
public float elapsedTime = 0f;
bool ToggleGUI = false;
bool isCreated = false;
Vector3 firstpos;
private void Update()
{
if(ToggleGUI == true)
{
OnGUI();
}
}
void OnTriggerStay(Collider other)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= 5.0f)
{
ToggleGUI = true;
}
}
void OnTriggerExit(Collider other)
{
elapsedTime = 0f;
}
void OnGUI()
{
if (ToggleGUI == true)
{
if (!isCreated)
{
firstpos = transform.position;
firstpos.y += 2f;
transform.position = firstpos;
isCreated = true;
}
}
}
after isCreated is set to true there is no place in your code where that variable is set again to false, therefore it can get inside the condition that moves the gameobject just once for the lifecycle of the script.
FYI when you pass a bool to an if you don't need to write ==true to have it execute when that variable is true, the name of the variable is enough
Your code seems a bit to complex for what you are trying to achieve.
If following your description what you want is just that the object goes up by 2f units every time you have stayed within a collider for 5 seconds I would simply use a Corouinte like e.g.
// The currently running Coroutine
private Coroutine _routine;
private int currentTriggers;
private void OnTriggerEnter(Collider other)
{
currentTriggers++;
// Start a routine if none is running already
if(_routine == null)
{
_routine = StartCorouine (Routine());
}
}
private void OnTriggerExit(Collider other)
{
currentTriggers--;
// If there are still other triggers do nothing
if(currentTriggers > 0) return;
// Cancel the routine if one is running
if(_routine != null)
{
StopCoroutine(_routine);
_routine = null;
}
}
IEnumerator Routine()
{
// As the name says wait 5 seconds
yield return new WaitForSeconds (5f);
// This is only reached if the routine wasn't stopped before the time passed
// Then move your object up
transform.position += Vector3.up * 2f;
_routine = null;
}
In general be very careful with your method names!
OnGUI is a special built-in event message that is called for handling events such as Keyboard or mouse input events, Repaint calls from the UI, etc. It might get called multiple times within a single frame and is absolutely not what you want to do here and for sure you should not call this method "manually" from Update.
Related
I used an item script that I found online that picks up an Item. It was intended for first-person raycasting but I changed it to detect if my player triggers the item. I wanted the item to lock to my player's hand 1 second after the animation plays. I tried invoking but I learned I can't do that with parameters. I then tried Coroutines but they were complicated and I could not get it to work. I made a new void that I invoke after 1 second which I want to start my PickItem void. However, I don't know how to do this. I don't understand how parameters work either.
public int number = 1;
Animator animator;
private int i;
private GameObject[] Item;
private bool inrange;
// Reference to the character camera.
[SerializeField]
private Camera characterCamera;
// Reference to the slot for holding picked item.
[SerializeField]
private Transform slot;
// Reference to the currently held item.
private PickableItem pickedItem;
My OnTriggerStay code.
private void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Item")
{
inrange = true;
if (inrange == true && (number % 2) == 1)
{
// Check if object is pickable
var pickable = other.gameObject.GetComponent<PickableItem>();
// If object has PickableItem class
if (pickable)
{
//Invoke delay after 1 seconds
Invoke("delay", 1f);
}
}
}
}
My delay void:
public void delay()
{
number = number + 1;
PickItem();
}
My PickItem void:
// I don't under stand this line of code either
/// <param name="item">Item.</param>
public void PickItem(PickableItem item)
{
// Assign reference
pickedItem = item;
// Disable rigidbody and reset velocities
item.Rb.isKinematic = true;
item.Rb.velocity = Vector3.zero;
item.Rb.angularVelocity = Vector3.zero;
// Set Slot as a parent
item.transform.SetParent(slot);
// Reset position and rotation
item.transform.localPosition = Vector3.zero;
item.transform.localEulerAngles = Vector3.zero;
item.GetComponent<MeshCollider>().enabled = false;
}
I know coroutines are probably better in this case but I could not get them working.
Coroutines make this simple. Use WaitForSeconds to create the delay:
IEnumerator DelayPickup(PickableItem item)
{
yield return new WaitForSeconds(1f);
number++;
PickItem(item);
}
Call it like this:
private void OnTriggerStay(Collider other)
{
if (other.gameObject.CompareTag("Item"))
{
inrange = true;
// inrange is true here by assignment, don't need to check again
if (number % 2 == 1)
{
// Check if object is pickable
var pickable = other.gameObject.GetComponent<PickableItem>();
// If object has PickableItem class
if (pickable)
{
StartCoroutine(DelayPickup(pickable));
}
}
}
}
By the way, they aren't called "voids", they're methods with a return type of void.
Using c#, im trying to fire a bullet every 3 seconds, so heres my workflow:
fire button is pressed
only fire if bool fireAgain is true
set bool fireAgain = false
start timer
timer finished = bool fireAgain = true
When debugging it seems to all work properly, but when I test it, Im still able to shoot like 10 bullets a second. So somehow it just doesnt care about the bool FireAgain being false and shoots anyway even if according to debug bool fireAgain is false at that moment.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain == true && Input.GetMouseButtonDown(0))
{
StartCoroutine("LoopRotation");
fireAgain = false;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
while (fireAgain == false && timer < bulletTime)
{
fireAgain = false;
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
} if (timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
And here is the code for the Coroutine:
IEnumerator LoopRotation()
{
pivot.transform.Rotate(triggerAngle,0,0);
GameObject bullet = ObjectPooler.SharedInstance.GetPooledObject();
if (bullet != null) {
bullet.transform.position = SSpawn.transform.position;
bullet.transform.rotation = SSpawn.transform.rotation;
bullet.SetActive(true);
}
yield return new WaitForSeconds(.1f);
pivot.transform.rotation = Quaternion.Slerp(transform.rotation, originalRotationValue, Time.deltaTime * rotationResetSpeed);
StopCoroutine("LoopRotation");
}
Enumerator LoopRotation was originally just to pivot the weapon a few degrees forwards and then backwards so it looks like a wack when you cast a spell, but now its also the shoot function, as it creates bullets.
You have a while loop within Update => this loop will completely run in one single frame => "immediately" will increase the timer until it is big enough => "immediately" will set your bool flag to true again!
What you rather would do is e.g.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
timer = 0;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
else if(timer < bulletTime)
{
// Only increase this ONCE per frame
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
if(timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
}
}
}
As alternative you could use Invoke and skip the timer in Update completely:
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
Invoke (nameof(AllowFireAgain), bulletTme);
}
}
private void AllowFireAgain()
{
fireAgain = true;
}
Note that your Coroutine doesn't make much sense to me. You are only rotating exactly once and only a really small amount.
The StopCoroutine in the end is unnecessary.
Also note: for debugging fine but later you should avoid to have Debug.Log running every frame in a built application. Even though the user doesn't see it the log is still created into the player log file and causes quite an amount of overhead.
Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
In my code, I am calling a Slow motion function when the player enter to a Trigger, but I want this function to stop after 1 second, I tried the code below but the slow motion didn't work. Do you have any idea?
Player Script:
public Rigidbody Ball;
public float Speed = 50f;
public TimeManager timeManager;
bool SlowOn;
bool ClickDone = false;
// Use this for initialization
void Start () {
StartCoroutine(SlowOff());
}
// Update is called once per frame
void FixedUpdate () {
if (!ClickDone){
if (Input.GetMouseButton (0)) {
ClickDone = true;
Ball.velocity = transform.forward * Speed;
}
}
}
private void OnTriggerEnter (Collider other) {
if(other.gameObject.CompareTag ("SlowMotionArea")) {
if (SlowOn) {
timeManager.DoSlowMotion();
}
}
}
IEnumerator SlowOff () {
yield return new WaitForSeconds(2.0f);
SlowOn = false;
}
TimeManager Script:
public float SlowDownFactor = 0.05f;
public float SlowdownLength = 2f;
public void DoSlowMotion () {
Time.timeScale = SlowDownFactor;
Time.fixedDeltaTime = 0.02f * Time.timeScale ;
}
There are a few problems with your code
You never set SlowOn to true so your line
timeManager.DoSlowMotion();
is never executed. Somewhere in your code you should call
SlowOn = true;
I'm guessing but it seems that you wanted to use that bool to prevent multiple calls of timeManager.DoSlowMotion()? In this case it should rather be something like
if(!SlowOn)
{
timeManager.DoSlowMotion();
SlowOn = true;
}
Why do you call StartCoroutine(SlowOff()); in your Start() method?
I guess you should rather remove that line and place it after
timeManager.DoSlowMotion();
StartCoroutine(SlowOff());
so the Coroutine is started everytime a SlowMotion starts.
It is not enough for the SlowMotion to stop to just set your SlowOn = false.
In your TimeManager you should rather store the original TimeScale and reset them in a second method:
public float SlowDownFactor = 0.05f;
public float SlowdownLength = 2f;
private float originalTimeScale;
private float originalFixedDeltaTime;
public void DoSlowMotion () {
// before changing store current values
originalTimeScale = Time.timeScale;
originalFixedDeltaTime = Time.fixedDeltaTime;
Time.timeScale = SlowDownFactor;
Time.fixedDeltaTime = 0.02f * Time.timeScale ;
}
public void ResetTimeScales()
{
Time.timeScale = originalTimeScale;
Time.fixedDeltaTime = originalFixedDeltaTime;
}
and than you also have to call that ResetTimeScales method from the player
IEnumerator SlowOff () {
yield return new WaitForSeconds(2.0f);
timeManager.ResetTimeScales();
SlowOn = false;
}
I guess you know that
yield return new WaitForSeconds(2.0f);
will also be affected by the changed Timescale so your SlowMotion currently would be longer than the expected 2 seconds namely 2 / SlowDownFactor = 40!
You could avoid this by using the Time.unscaledDeltaTime as a countdown like
private IEnumerator SlowOff()
{
// since your title actually claims you want to reset after 1 second
float timer = 1;
while (timer > 0)
{
timer -= Time.unscaledDeltaTime;
yield return null;
}
timeManager.ResetTimeScales();
SlowDown = false;
}
I have a 2D shooting game on Unity using C# now and I want to increase the fire rate of the ship for 5 seconds when it gets a power up item. It kinda works but when the ship gets the power up, the fire rate does not change until the button is released and pressed again. Is there a way to change the fire rate as soon as it gets the power up even while the button is being pressed? Also, the power up function is something that I came up with and if there is a better way to make the power up functions, that will be very helpful too. Thanks in advance :)
void Update(){
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireRate);
}
}
void PowerUp()
{
Upgrade = true;
timeLeft = +5f;
if (Upgrade == true)
{
fireRate = 0.1f;
}
if (timeLeft <= 0)
{
Upgrade = false;
fireRate = 0.5f;
}
}
You should pass an reference type to the Fire coroutine instead of a float fireRate.
Just wrap fireRate in a class should work:
class FireData
{
public float fireRate = 0.1;
}
Then in your script,
FireData fireData = new FireData { fireRate = 0.5f };
void Update(){
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireData);
}
}
void PowerUp()
{
Upgrade = true;
timeLeft = +5f;
if (Upgrade == true)
{
fireData.fireRate = 0.1f;
}
if (timeLeft <= 0)
{
Upgrade = false;
fireData.fireRate = 0.5f;
}
}
In Fire() coroutine, use this fireData.fireRate to get the fireRate.
By the way, I think your power up functions is good enough.
But the way of using coroutine is not correct.Don't call InvokeRepeating on the same function multiple times.
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireRate);
}
Instead, you should use a bool value to control when the fire will start.
if (Input.GetKeyDown(KeyCode.Space)){
fireData.Firing = true;
}
if(fireData.Firing)
{
InvokeRepeating("Fire", 0.000001f, fireRate);
}
Also remember to add a logic to stop firing via StopCoroutine.