I created a bonus for the game, which increase the main player’s size when you pick him. I wrote a script, but timer doesn't work when the player and bonus are collide, how to fix it?
When you start coroutine in Start() method, the timer work correctly, but I want to start timer after collision.
Bonus: player pick him and start timer for 10 sec. In this period, player's size change for 10 sec, then return to the original size.
public GameObject effect;
public GameObject Player;
bool bol = false;
float Timeremaining = 3f;
float CurrentTime;
public void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.name == "Cube")
{
bol = true;
Destroy(this.gameObject);
Instantiate(effect, transform.position, Quaternion.identity);
RipplePostProcessor.bloom++;
Debug.Log(bol);
StartTimeEvent();
}
}
public void StartTimeEvent()
{
if (bol == true)
StartCoroutine(StartCountDown());
else
StopCoroutine("StartCountDown");
}
public IEnumerator StartCountDown()
{
CurrentTime = Timeremaining;
while (CurrentTime > 0)
{
Debug.Log("CountDown :" + CurrentTime);
Player.transform.localScale = new Vector3(5, 5, 5);
yield return new WaitForSeconds(1.0f);
CurrentTime--;
if (CurrentTime < 1)
{
bol = false;
Player.transform.localScale = new Vector3(3, 3, 1);
Debug.Log(bol);
}
}
}
Related
I am trying to set a fire rate in Unity so that when I hold down up arrow, I will shoot a projectile up every 1 second. Currently my code is shooting a projectile every frame update, even though I have a Coroutine set up.
public GameObject bulletPrefab;
public float bulletSpeed;
public float fireRate = 1f;
public bool allowFire = true;
void Update()
{
//shooting input
if (Input.GetKey(KeyCode.UpArrow) && allowFire == true)
{
StartCoroutine(Shoot("up"));
}
}
IEnumerator Shoot(string direction)
{
allowFire = false;
if (direction == "up")
{
var bulletInstance = Instantiate(bulletPrefab, new Vector3(transform.position.x, transform.position.y, transform.position.z + 1), Quaternion.identity);
bulletInstance.GetComponent<Rigidbody>().AddForce(Vector3.forward * bulletSpeed);
}
yield return new WaitForSeconds(fireRate);
allowFire = true;
}
Coroutine
You can use the coroutine, but since you're calling it in an Update loop you much wait for it to finish before starting another one.
Coroutine currentCoroutine;
if(currentCoroutine == null)
currentCoroutine = StartCoroutine(DoShoot());
IEnumerator DoShoot() {
// Shoot.
yield return new WaitForSeconds(1f);
currentCoroutine = null;
}
Timestamp
You can also use a timestamp for when cooldown is ready. It's basically current time plus some duration.
float cooldown = 1f;
float cooldownTimestamp;
bool TryShoot (Vector2 direction) {
if (Time.time < cooldownTimestamp) return false;
cooldownTimestamp = Time.time + cooldown;
// Shoot!
}
I usually end up doing something like:
Variables
[Serializefield] float shootDelay = 1f;
float T_ShootDelay
Start()
T_ShootDelay = shootDelay;
Update()
if(T_ShootDelay < shootDelay)
T_ShootDelay += Time.deltaTime;
ShootInput()
if(T_ShootDelay >= shootDelay)
{
T_ShootDelay = 0;
Shoot();
}
What this does is:
Check if the shootDelay timer is less than the shootDelay.
Add up the timer by 1 per second.
Check the timer's status every time you want to shoot.
Set the timer to 0 after shooting
Considering a M4 AR that fires at least 700 rounds per minute.
700 rounds / 60 (seconds) ~= 11,66 rounds per second
1 second / 11 rounds ~= 0,085 seconds of delay between each round
You could simply try this:
yield return new WaitForSeconds(1 / (fireRate / 60f));
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 1 year ago.
This post was edited and submitted for review 1 year ago and failed to reopen the post:
Original close reason(s) were not resolved
After the enemy collides with the player I want him to stop for some time, so it doesnt chase after him all the time. It looks weird. Like, add an Invoke function or something(not a pro). Please implement it right away (im just a beginner) Here is the code(I was following some 7 hour tutorial):
// Experience
public int xpValue = 1;
//Particle system
public GameObject effect;
// Logic
public float triggerLength = 1;
public float chaseLength = 5;
public bool chasing;
public bool collidingWithPlayer;
private Transform playerTransform;
private Vector3 startingPosition;
// Hitbox
public ContactFilter2D filter;
private BoxCollider2D hitbox;
private Collider2D[] hits = new Collider2D[10];
protected override void Start()
{
base.Start();
playerTransform = GameManager.instance.player.transform;
startingPosition = transform.position;
hitbox = transform.GetChild(0).GetComponent<BoxCollider2D>();
}
protected void FixedUpdate(){
//Is the player in range?
if(Vector3.Distance(playerTransform.position, startingPosition) < chaseLength)
{
if(Vector3.Distance(playerTransform.position, startingPosition) < triggerLength)
chasing = true;
if (chasing){
if(!collidingWithPlayer){
UpdateMotor((playerTransform.position - transform.position).normalized);
}
}
else{
UpdateMotor(startingPosition - transform.position);
}
}
else{
UpdateMotor(startingPosition - transform.position);
chasing = false;
}
//Check for overlap
collidingWithPlayer = false;
boxCollider.OverlapCollider(filter, hits); // null reference expection
for (int i = 0; i < hits.Length; i++)
{
if (hits[i] == null)\
continue;
if(hits[i].tag == "Fighter" && hits[i].name == "Player"){
collidingWithPlayer = true;
}
//this array is not cleaned up, so we do it ourself
hits[i] = null;
}
}
You will probably want to use either Coroutines or timers to achieve this.
This is untested and there is likely some optimization that can be done on this.
Timers
private bool hasAttacked = false;
private float attackTimer = 0;
private float waitTime = 0.5f;
private void Update()
{
if (!hasAttacked)
{
hasAttacked = true;
attack();
attackTimer = Time.time + waitTime;
}
else
{
if ( Time.time > attackTimer)
{
hasAttacked = false;
}
}
}
This snippet will run an attack function, then wait for 0.5 seconds, and run the code again.
Easiest solution would be to look into coroutines. They run on the main thread but can be paused and wait for delays or events.
bool CanAttack()
{
return true; //Check if can attack here
}
void Attack()
{
//Put your attacking code here
}
void Start()
{
StartCoroutine(AttackLoop());
}
IEnumerator AttackLoop()
{
while (true)
{
//Check if enemy can attack
if (CanAttack())
{
Attack();
//Waits 2 seconds before continuing
yield return new WaitForSeconds(2f);
}
//Wait one frame
//Prevents an infinite loop
yield return null;
}
}
So I have been trying to make a game recently and I have ran into a unexpected bug.
As I run the script I have wrote I can shoot 10 bullets out of my gun, then it plays a reload sound and waits two seconds using coroutines and it adds 1200 to my clip for no reason? So I added the if statement telling it that if the clip size goes over 10 then revert it to 10 but now it goes to 19 for a few seconds then back to 10.
I am unaware if I am being a idiot or something but would be glad for some help, also sorry if this is a duplicate but i was unable to find anything similar in c#.
EDIT: i have fixed the bug now, thanks for the help, ill share the new code after the old code for future reference!
My old code:
{
public float fireRate = 10;
public float damage = 15;
public int clipsize;
float timeUntilFire = 0;
public float ReloadSpeed = 2.5f;
float reloadtime;
public Transform firePoint;
public GameObject bullet;
public AudioSource gunshotaudio;
public AudioSource ReloadSound;
void Awake()
{
gunshotaudio = GetComponent<AudioSource>();
clipsize = 10;
}
// Update is called once per frame
void Update()
{
// if the button pressed is fire 1 and
if (Input.GetButtonDown("Fire1") && clipsize != 0)
{
timeUntilFire = Time.time / fireRate;
gunshotaudio.Play();
Shoot();
clipsize -= 1;
Debug.Log(clipsize);
}
if(clipsize <= 0)
{
ReloadSound.Play();
StartCoroutine(Wait());
StopCoroutine(Wait());
}
if(clipsize >= 11)
{
clipsize = 10;
}
}
IEnumerator Wait()
{
yield return new WaitForSecondsRealtime(2);
clipsize += 10;
}
void Shoot()
{
gunshotaudio.Play();
Instantiate(bullet, firePoint.position,firePoint.rotation);
}
}
** My New Code: **
{
public float fireRate = 10;
public float damage = 15;
public int clipsize;
float timeUntilFire = 0;
public float ReloadSpeed = 2.5f;
float reloadtime;
bool CanShoot;
bool IsReloading;
bool reloading;
public Transform firePoint;
public GameObject bullet;
public AudioSource gunshotaudio;
public AudioSource ReloadSound;
void Awake()
{
gunshotaudio = GetComponent<AudioSource>();
clipsize = 10;
}
void Start()
{
if (clipsize > 0)
{
CanShoot = true;
}
}
// Update is called once per frame
void Update()
{
// if the button pressed is fire 1 and
if (Input.GetButtonDown("Fire1") && clipsize != 0)
{
timeUntilFire = Time.time + fireRate;
gunshotaudio.Play();
Shoot();
clipsize--;
Debug.Log(clipsize);
}
// reloading
if(Input.GetKeyDown(KeyCode.R))
{
IsReloading = true;
ReloadSound.Play();
}
if (IsReloading)
{
StartCoroutine(Wait());
}
}
IEnumerator Wait()
{
if(reloading == false)
{
if (IsReloading)
{
reloading = true;
CanShoot = false;
yield return new WaitForSeconds(1);
clipsize = 10;
IsReloading = false;
Debug.Log(clipsize);
CanShoot = true;
reloading = false;
}
else
{
}
}
}
void Shoot()
{
gunshotaudio.Play();
Instantiate(bullet, firePoint.position,firePoint.rotation);
}
}
You aren't checking to see if a reload is already in process, so you are queuing up a bunch of reload events which all add +10 to the clip resulting in extras.
Add a reloading bool and check that before you initiate your coroutine and instead of adding 10 to the clip, set the clip to 10.
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 4 years ago.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
originalDoorPosition = door.position;
}
// Update is called once per frame
void Update()
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
{
StartCoroutine(DoorSpeedWaitForSeconds());
}
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return new WaitForSeconds(3);
}
}
Making StartCoroutine inside the Update is a bad idea. But I want that it will take one random speed when running the game then will wait 3 seconds and change to a new random speed then wait another 3 seconds and change for another new random speed and so on.
And while it's waiting 3 seconds to keep the current speed constant until the next change.
Update:
This is what I tried:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
public bool IsGameRunning = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
IsGameRunning = true;
originalDoorPosition = door.position;
StartCoroutine(DoorSpeedWaitForSeconds());
}
// Update is called once per frame
void Update()
{
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while (IsGameRunning)
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return delay;
}
}
}
But there are two problems.
The first problem is that every 3 seconds, when it's changing the speed of the door, it's also changing the door position from its current position. So it looks like the door position is jumping to another position and then continue from there. How can I make that it will change the speed meanwhile the door keep moving from its current position ?
Second problem is how can I change the randomDorrSpeed flag so it will take effect while the game is running? I Want that if randomDorrSpeed is false use the original speed of the door (1.0) and if it`s true use the random speed.
You already know that the coroutine should start from Start:
void Start()
{
//initialization
StartCoroutine(DoorSpeedWaitForSeconds());
}
So make the coroutine a loop with the proper terminate condition:
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while(IsGameRunning)
{
if(randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
if(!randomDoorSpeed)
doorSpeed = 1;//reset back to original value
yield return delay;//wait
}
}
For the other question you asked, if you think about it you can't possibly use ping pong with dynamic speed based on Time.time. You need to change it like this:
bool isRising = true;
float fraq = 0;
void Update()
{
if (isRising)
fraq += Time.deltaTime * doorSpeed;
else
fraq -= Time.deltaTime * doorSpeed;
if (fraq >= 1)
isRising = false;
if (fraq <= 0)
isRising = true;
fraq = Mathf.Clamp(fraq, 0, 1);
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
fraq);
}
You can solve the original problem without coroutines:
public float timeBetweenChangeSpeed = 3f;
public float timer = 0;
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// If 3 seconds passed, time to change speed
if(timer >= timeBetweenChangeSpeed)
{
timer = 0f;
//Here you call the function to change the random Speed (or you can place the logic directly)
ChangeRandomSpeed();
}
}
And about opening and closing doors. Here is a script I used to control doors in a maze game I worked in:
You need to set empty gameObjects with to set the boundaries, that is until what point you want to move the door one opening or when closing. You place this empty gameobjects in your scene and link them to the scrip in the correct field. The script will take the transform.position component on it's own. There is also a trigger enter to activate the door when a character approaches. If you dont need that part, I can edit the code tomorrow.
You can use this script also to move platforms, enemies... in general anything which can move in a straight line.
using UnityEngine;
using System.Collections;
public class OpenDoor : MonoBehaviour {
// define the possible states through an enumeration
public enum motionDirections {Left,Right};
// store the state
public motionDirections motionState = motionDirections.Left;
//Variables for State Machine
bool mOpening = false;
bool mClosing = false;
//bool mOpened = false;
//OpenRanges to open/close the door
public int OpenRange = 5;
public GameObject StopIn;
public GameObject StartIn;
//Variables for Movement
float SpeedDoor = 8f;
float MoveTime = 0f;
int CounterDetections = 0;
void Update () {
// if beyond MoveTime, and triggered, perform movement
if (mOpening || mClosing) {/*Time.time >= MoveTime && */
Movement();
}
}
void Movement()
{
if(mOpening)
{
transform.position = Vector3.MoveTowards(transform.position, StopIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StopIn.transform.position) <= 0)
mOpening = false;
}else{ //This means it is closing
transform.position = Vector3.MoveTowards(transform.position, StartIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StartIn.transform.position) <= 0)
mClosing = false;
}
}
// To decide if door should be opened or be closed
void OnTriggerEnter(Collider Other)
{
print("Tag: "+Other.gameObject.tag);
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player" || Other.gameObject.tag == "Elevator")
{
CounterDetections++;
if(!mOpening)
Opening();
}
}
void OnTriggerStay(Collider Other)
{
if(Other.gameObject.tag == "Elevator")
{
if(!mOpening)
Opening();
}
}
void OnTriggerExit(Collider Other)
{
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player")
{
CounterDetections--;
if(CounterDetections<1)
Closing();
}
}
void Opening()
{
mOpening = true;
mClosing = false;
}
void Closing()
{
mClosing = true;
mOpening = false;
}
}
Using timer and setting an interval. The delegate event will fire every time the interval is reached.
var t = new Timer {Interval = 3000};
t.Elapsed += (sender, args) => { /* code here */};
I want to make my player invulnerable from objects hitting him for a few seconds after he resets back to the center of the game, meaning I don't want anything to hurt him and I don't want the player to move for 5 seconds, but i'm not sure of how to do that! I searched it up but results that I found doesn't match up with my code. Anyway this is my playermovement script:
private Animator anim;
public float speed = 15f;
public static Vector3 target;
private bool touched;
private bool canmove;
Vector3 startPosition;
void Start () {
target = transform.position;
anim = GetComponent<Animator> ();
}
void Update () {
if (Input.GetMouseButtonDown (0)) {
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = 10; // distance from the camera
target = Camera.main.ScreenToWorldPoint(mousePosition);
target.z = transform.position.z;
}
transform.position = Vector3.MoveTowards (transform.position, target, speed * Time.deltaTime);
var movementDirection = (target - transform.position).normalized;
if (movementDirection.x != 0 || movementDirection.y != 0) {
anim.SetBool ("walking", false);
anim.SetFloat("SpeedX", movementDirection.x);
anim.SetFloat("SpeedY", movementDirection.y);
anim.SetBool ("walking", true);
}
}
void FixedUpdate () {
float LastInputX = transform.position.x - target.x;
float LastInputY = transform.position.y - target.y;
if (touched) {
if (LastInputX != 0 || LastInputY != 0) {
anim.SetBool ("walking", true);
if (LastInputX < 0) {
anim.SetFloat ("LastMoveX", 1f);
} else if (LastInputX > 0) {
anim.SetFloat ("LastMoveX", -1f);
} else {
anim.SetFloat ("LastMoveX", 0f);
}
if (LastInputY > 0) {
anim.SetFloat ("LastMoveY", 1f);
} else if (LastInputY < 0) {
anim.SetFloat ("LastMoveY", -1f);
} else {
anim.SetFloat ("LastMoveY", 0f);
}
}
}else{
touched = false;
anim.SetBool ("walking", false);
}
}
}
And this is my player health script:
public int curHealth;
public int maxHealth = 3;
Vector3 startPosition;
bool invincible = false;
void Start ()
{
curHealth = maxHealth;
startPosition = transform.position;
}
void Update ()
{
if (curHealth > maxHealth) {
curHealth = maxHealth;
}
if (curHealth <= 0) {
Die ();
}
}
void Die ()
{
//Restart
Application.LoadLevel (Application.loadedLevel);
}
public void Damage(int dmg)
{
curHealth -= dmg;
Reset();
}
void Reset ()
{
transform.position = startPosition;
PlayerMovement.target = startPosition;
}
}
So to be more simple, I want to make my player invulnerable (once he reset back in the center of the screen) from getting hit from enemies for 5 seconds and people who are playing my player can not move for 5 seconds. Thank you! (My game is a topdown 2d click to move game)
I think this should do the trick. At least point you in the right direction.
Simple explanation, i changed invincible to be a time, a time in the future.
Then we simply add a time in the future to it at some event, and from that event onward to the point in the future we want him invincible.
So at every event where he would take damage we check, is now in the future of invincible or is invincible in the future of now. that is your bool.
DateTime invincible = DateTime.Now;
public void Damage(int dmg)
{
if(invincible <= DateTime.Now)
{
curHealth -= dmg;
Reset();
}
}
void Reset ()
{
transform.position = startPosition;
PlayerMovement.target = startPosition;
invincible = DateTime.Now.AddSeconds(5);
}
if you want a helper method like this
bool IsInvincible(){
return invincible <= DateTime.Now;
}
Then you can change out the if(invincible <= DateTime.Now) with if(IsInvincible()) in your public void Damage(int dmg)
In game development, you have to keep in mind that everything happens within a frame update. I like to use coroutines to implement invincibility frames in Unity. A coroutine is simply a method that runs in parallel to the main update loop and resumes where it left off in the next update.
float dmg = 100.0f;
bool isHitByEnemy = false
bool isInvulnerable = false
void Demage()
{
//some dmg logic
if (isHitByEnemy && isInvulnerable == false)
{
StartCoroutine("OnInvulnerable");
}
}
IEnumerator OnInvulnerable()
{
isInvulnerable = true;
dmg = 0.0f;
yield return new WaitForSeconds(0.5f); //how long player invulnerable
dmg = 100.0f;
isInvulnerable = false;
}