using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RedHP : MonoBehaviour
{
public float HP = 5;
public GameObject BlueWon;
public GameObject Restart;
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime());
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime()
{
yield return new WaitForSeconds(1);
Destroy(trig.gameObject);
}
}
I simply want to destroy my object after too little period of time to make it look better. In IEnumerator I can't access trig.gameObject because it is defined in OnTriggerEnter2D. Is there a way to access this value?
I also tried to put IEnumerator in OnTriggerEnter2D it also didn't work. Kinda newbie
You don't have to do that. The Destroy function can take a second parameter as a delay time before the Object is destroyed.
Destroy(trig.gameObject, 1f);
If you still want to use coroutine to do this, simply make the BowlDestroyTime function to take GameObject as parameter then pass the GameObject from the OnTriggerEnter2D function to the BowlDestroyTime function to be destroyed.
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime(trig.gameObject));
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime(GameObject tartgetObj)
{
yield return new WaitForSeconds(1);
Destroy(tartgetObj);
}
Related
I'm sorry for any messy code, I'm relatively new to this. I made a working teleport in Unity but whenever I teleport from one of the teleports to the other, I wanna make it so there's a 5 second cooldown before you can use the teleporter again. So I used IEnumerator, added 5 seconds before "justTeleported" became false again, but when I teleported, I instantly got teleported back, and had to wait 5 seconds before I could try again. So my though was maybe I'm touching the trigger too quickly, before it can become false, that's why I added the two seconds. But now, whenever I get on the teleporter, it goes from true to false to true a couple times, and then I eventually get teleported back to where I came from. If anyone could help, I would be very thankful. Thank you.
{
public Transform Destination;
bool justTeleported;
public GameObject Player = GameObject.FindGameObjectWithTag("Player");
// Start is called before the first frame update
void Start()
{
justTeleported = false;
}
private void Update()
{
print(justTeleported)
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.tag == "Player" && justTeleported == false)
{
StartCoroutine("Cooldown");
}
}
IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(2f);
Player.transform.position = Destination.transform.position;
yield return new WaitForSecondsRealtime(5f);
justTeleported = false;
}
Because each of the teleports has its own bool justTeleported, setting the first true doesn't automatically set the other true, so you need some way to tell the Destination teleport script to start it's own cooldown coroutine. Here's my test script, it works for me.
using System.Collections;
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Teleport Destination = null;
public float CooldownTime = 5f;
private bool justTeleported = false;
public void StartCoolDown()
{
StartCoroutine(Cooldown());
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player") && !justTeleported) //CompareTag("tag") is better than .tag == "tag"
{
TeleportPlayer(coll.gameObject);
}
}
private void TeleportPlayer(GameObject player)
{
if (Destination) //if Destination is not null
{
StartCoolDown();
Destination.StartCoolDown(); //tell Destination to cooldown
player.transform.position = Destination.transform.position;
}
}
private IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(CooldownTime);
justTeleported = false;
}
}
Teleport1 (Destination: Teleport2)
Teleport2 (Destination: Teleport1)
My unity project is freezing and there are no errors before clicking play button after that it freezes the code:
In this code I am trying to detect if the player is triggering enemy collider with OnTriggerStay2D
so if it does I want the code to add score every second with IEnumerator function. But if the player is not colliding with the enemy for more than 2 seconds it gets destroyed.
The code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Trigger : MonoBehaviour
{
public GameObject other;
bool touching = false;
int score = 0;
public void Update()
{
AddScore();
}
private void OnTriggerStay2D(Collider2D other)
{
Debug.Log("Touching");
touching = true;
}
public void AddScore()
{
if(touching == true)
{
while (touching == true)
{
score++;
StartCoroutine(wait());
}
Debug.Log(score);
}
else
{
Debug.Log("Not Touching");
StartCoroutine(waiter());
}
}
IEnumerator waiter()
{
yield return new WaitForSecondsRealtime(2);
Destroy(other);
}
IEnumerator wait()
{
yield return new WaitForSecondsRealtime(1);
}
}
Just delete while (touching == true), that is breaking your game.
The if condition should be enough for the logic.
I am trying to learn unity. This is something I would use in Javascript so I was hoping to find a way to do this in C#, where you take the collision variable and use the reference to have another function clean up the variables after you are finished.
Edit
I have been practicing since posting this, I am trying to pass a reference to another class to handle the turn on and off of the variable.
private void OnTriggerEnter(Collider collision) {
if(collision.gameObject.CompareTag("Enemy")) {
PowerUpRoutine(ref collision.gameObject.GetComponent<enemy>().powerup);
Destroy(gameObject);
}
if (collision.gameObject.CompareTag("Player")) {
PowerUpRoutine(ref collision.gameObject.GetComponent<player>().powerup);
Destroy(gameObject);
}
}
IEnumerator PowerUpRoutine(float target) {
target = 1;
yield return new WaitForSeconds(5);
target = 0;
}
I have also tried not passing powerup, but just the object and it still errors. Is there anyway to accomplish this?
Original
private void OnTriggerEnter(Collider collision) {
if(collision.gameObject.CompareTag("Enemy")) {
collision.gameObject.GetComponent<enemy>().powerup = 1;
StartCoroutine(removePower(collision.gameObject.GetComponent<enemy>()));
Destroy(gameObject);
}
if (collision.gameObject.CompareTag("Player")) {
collision.gameObject.GetComponent<player>().powerup = 1;
StartCoroutine(removePower(collision.gameObject.GetComponent<player>()));
Destroy(gameObject);
}
}
IEnumerator removePower(GameObject target) {
yield return new WaitForSeconds(5);
target.powerup = 0;
}
First if all there is a general flaw in your approach: When you
Destroy(gameObject);
this object this component is attached to then also all Coroutines are immediately canceled.
I would therefore start the coroutine rather on the target itself (see example below).
And then your classes should have a common interface or base class like for example
Solution 1 - Common Base Class
public abstract class Character : MonoBehaviour
{
public float powerup;
// And other common members
}
And then you inherit
public class Player : Character
{
// Additional player specific stuff
}
and
public class Enemy : Character
{
// Additional enemy specific stuff
}
Solution 2 - Common Interface
if you rather want to go for an interface
public interface ICharacter
{
float powerup { get; set; }
}
And then both your classes have to implement that
public class Player : MonoBehaviour, ICharacter
{
public float powerup { get; set; }
// Additional player specific stuff
}
and
public class Enemy : MonoBehaviour, ICharacter
{
public float powerup { get; set; }
// Additional player specific stuff
}
And then your collision code could simply be
private void OnTriggerEnter(Collider collision)
{
// TryGetComponent now finds anything inherited from Character
// you don't even need to check the tags
if(collision.TryGetComponent<Character>(out var character))
// Or if using the interface
//if(collision.TryGetComponent<ICharacter>(out var character)
{
// Let the character run this coroutine without even having to know what it does
character.StartCoroutine(PowerUpRoutine(character));
// This is now ok and not terminating the Coroutine since the routine is run by character itself
Destroy(gameObject);
}
}
IEnumerator PowerUpRoutine(Character target)
// or if using the interface accordingly
//IEnumerator PowerUpRoutine(ICharacter target)
{
// Have in mind though that this routine is now running on a different object
// at a time where this component is already destroyed => You can't reference to anything of this component in here!
target.powerup = 1;
yield return new WaitForSeconds(5);
target.powerup = 0;
}
Solution 3 - Simple Overload
Or as last resort if the before two is not an option for whatever reason you could of course also simply have two overloads
IEnumerator PowerUpRoutine(Player target)
{
target.powerup = 1;
yield return new WaitForSeconds(5);
target.powerup = 0;
}
IEnumerator PowerUpRoutine(Enemy target)
{
target.powerup = 1;
yield return new WaitForSeconds(5);
target.powerup = 0;
}
and then do
private void OnTriggerEnter(Collider collision)
{
if(collision.TryGetComponent<Player>(out var player))
{
player.StartCoroutine(PowerUpRoutine(player));
Destroy(gameObject);
}
else if(collision.TryGetComponent<Enemy>(out var enemy))
{
enemy.StartCoroutine(PowerUpRoutine(enemy));
Destroy(gameObject);
}
}
Note: Coroutines are also stopped when the MonoBehaviour is destroyed or if the GameObject the MonoBehaviour is attached to is disabled.
You should destroy the gameobject when the coroutine finishes.
StartCoroutine(removePower(collision.gameObject.GetComponent<enemy>()));
//Destroy(gameObject);
IEnumerator removePower(GameObject target) {
yield return new WaitForSeconds(5);
target.powerup = 0;
Destroy(gameObject);
}
Or run the coroutine on another gameobject
var enemy = collision.gameObject.GetComponent<enemy>();
enemy.StartCoroutine(removePower(enemy));
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 am using the Unity Engine to make a 2D game... to give you an idea of what it looks like, there is a generic space background, 4 rocks bouncing around the screen, and a tie fighter. My goal was to make the tie fighter explode (Which I did succeed in doing), destroy itself, and have a prefab take its place. I am new to c#, so I don't know much of the API. I tried using the second script to destroy the tie fighter, then instantiate a prefab... Now, whenever I run the game, it spawns enough clones to the point where Unity crashes and I do not know how to fix it. I tried googling stuff and doing a manual fix (hence the bools), but nothing seems to be working. Any help would be greatly appreciated. Also, please don't just comment; write it as an answer so that I can mark it correct if it works. Here are the scripts (I am assuming the error is in the second one, but I included both for reference):
First Script:
using UnityEngine;
using System.Collections;
public class tfScript : MonoBehaviour
{
Vector3 tfPos;
Vector3 worldPos;
float mousePosInBlocksx;
float mousePosInBlocksy;
int lives;
public Sprite tieFight; // Drag your first sprite here
public Sprite kaboom; // Drag your second sprite here
private SpriteRenderer spriteRenderer;
float makeThingsGoBoom;
// Use this for initialization
public void Start ()
{
tfPos = new Vector3 (3f, 3f, -4f);
lives = 20;
spriteRenderer = GetComponent<SpriteRenderer>(); // we are accessing the SpriteRenderer that is attached to the Gameobject
if (spriteRenderer.sprite == null) // if the sprite on spriteRenderer is null then
{
spriteRenderer.sprite = tieFight; // set the sprite to sprite1
}
}
// Update is called once per frame
public void Update ()
{
GameObject controller = GameObject.Find ("controller");
gameController gameCon = controller.GetComponent<gameController> ();
mousePosInBlocksx = ((Input.mousePosition.x / Screen.width) * 16);
mousePosInBlocksy = ((Input.mousePosition.y / Screen.width) * 12)+2;
tfPos.x = Mathf.Clamp (mousePosInBlocksx, .5f, 15.5f);
tfPos.y = Mathf.Clamp (mousePosInBlocksy, .5f, 11.5f);
this.transform.position = tfPos;
if (makeThingsGoBoom == 0)
{
gameCon.Update();
}
}
public void ChangeTheDarnSprite ()
{
if (spriteRenderer.sprite == tieFight) { // if the spriteRenderer sprite = sprite1 then change to sprite2
spriteRenderer.sprite = kaboom;
}
else
{
spriteRenderer.sprite = tieFight;
}
}
public void OnCollisionEnter2D (Collision2D collider)
{
if (collider.gameObject.name.Contains("spacerock") )
{
lives--;
print (getLives ());
}
if (collider.gameObject.name.Contains("spacerock")) // If the space bar is pushed down
{
spriteRenderer.sprite = kaboom;
makeThingsGoBoom = 0;
}
}
public void increaseLives()
{
lives++;
}
public double getLives()
{
return lives;
}
}
Second Script:
using UnityEngine;
using System.Collections;
public class gameController : MonoBehaviour
{
public GameObject tf;
public GameObject tfpf;
public bool iBlowedUp = false;
public void Start()
{
}
public void Update ()
{
boom ();
}
public void boom()
{
iBlowedUp = true;
if (iBlowedUp = true)
{
StartCoroutine (waitForIt ());
Destroy (tf);
tfpf = Instantiate (Resources.Load ("Prefabs/tfpf")) as GameObject;
iBlowedUp = false;
}
}
public IEnumerator waitForIt()
{
print ("Bob lives #2!");
yield return new WaitForSeconds (1);
print ("John is a turtle #2!");
}
}
You are calling following method in a Update function which is executed constantly.
public void boom()
{
iBlowedUp = true;
if (iBlowedUp = true)
{
StartCoroutine (waitForIt ());
Destroy (tf);
tfpf = Instantiate (Resources.Load ("Prefabs/tfpf")) as GameObject;
iBlowedUp = false;
}
}
An if iBlowedUp = true; if (iBlowedUp = true){ doesn't make sense, because the statement is true always.
It should be similar to:
public void boom()
{
if (iBlowedUp == true)
{
iBlowedUp = false;
StartCoroutine (waitForIt ());
Destroy (tf);
tfpf = Instantiate (Resources.Load ("Prefabs/tfpf")) as GameObject;
}
}
Probably you want to set iBlowedUp to true somewhere else. As I consider in a tfScript.Update() method, instead of calling Update method.
if (makeThingsGoBoom == 0)
{
gameCon.iBlowedUp = true;
//gameCon.Update();
}
if (iBlowedUp = true)
{
iBlowedUp = false;
StartCoroutine (waitForIt ());
Destroy (tf);
tfpf = Instantiate (Resources.Load ("Prefabs/tfpf")) as GameObject;
}
before instantiate make isBlowedUp false, I cant say ı understood well this "Unity crashes". there is some complexity in your code. I hope you fix them as well