im doing my first game and im trying to detect the enemies inside an circle area around my player.
i have two problems right now:
-When a start the game, the circlecollider and player collider is detected as enemies even when i use the compare tag "Enemy"
-My corroutine dont refresh every 2s, and only detect colliders one time when the game start
public class ItemDamage : MonoBehaviour
{
[SerializeField] int damage;
[SerializeField] Collider2D[] objectsInsideArea;
Vector2 radiusOfDamage;
int radius;
public void Start()
{
radiusOfDamage = new Vector2(radius, 0f);
StartCoroutine(DamageEnemy());
}
bool IsEnemy(string tag)
{
for (int i = 0; i < objectsInsideArea.Length; i++)
if (objectsInsideArea[i].gameObject.CompareTag("Enemy"))
{
Debug.Log("object {i} is an Enemy");
return true;
} else
{
Debug.Log("object {i}");
}
return false;
}
IEnumerator DamageEnemy()
{
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (bool IsEnemy in objectsInsideArea)
{
Debug.Log("You damage the enemy");
}
yield return new WaitForSeconds(2);
}
}
For coroutine to repeat itself, it has to start itself in the end again like this:
IEnumerator DamageEnemy()
{
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (bool IsEnemy in objectsInsideArea)
{
Debug.Log("You damage the enemy");
}
yield return new WaitForSeconds(2);
StartCoroutine(DamageEnemy());
}
Also instead of coroutine for this, you can use InvokeRepeating method.
You don't have to use a predefined array of objects, but rather use SphereRaycast method where you specify layer of objects to look for.
And your enemies can be located in the specific layer.
A Caroutine does not start it self.
For the other problem. objectsInsideArea is a Collider array and not a bool array. You cannot check it this way. Your code must look like this:
IEnumerator DamageEnemy()
{
while(someBoolOrTrue) {
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (var collider in objectsInsideArea)
{
if(collider.tag.Equals("Enemy")) {
Debug.Log("You damage the enemy");
}
}
yield return new WaitForSeconds(2);
}
}
Related
Im working on bomb system in Unity 3D. I have uploaded some explosion effects from Unity Asset Store and I want implement them into my project. I want to plant bomb with key "K" then wait 3sec to detonate it with explosion effect and give some damage to nearby objects. The problem is that explosion appears in such different position as it should. In my opinion this is Editor problem , the code looks fine. I will give you some screenshoots(https://drive.google.com/file/d/19Yzymch9RdTa-E6RkbvvyfzMWjMJHo52/view?usp=sharing)and my bomb script :
public class BoombScript : MonoBehaviour
{
public GameObject boombEffect;
[SerializeField]
private float radius;
[SerializeField]
private float force;
[SerializeField]
private int explosiveDamage;
public void Explode()
{
Instantiate(boombEffect, transform.position, Quaternion.identity);
Debug.Log("Transform" + transform);
Debug.Log("Position" + transform.position);
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
foreach(Collider rangedObject in colliders)
{
GateScript Gate = rangedObject.GetComponent<GateScript>();
Rigidbody rb = rangedObject.GetComponent<Rigidbody>();
if(Gate != null)
{
Gate.GateDestroy(explosiveDamage);
}
if(rb != null)
{
rb.AddExplosionForce(force, transform.position, radius);
}
}
}
public IEnumerator WaitForExplode()
{
yield return new WaitForSeconds(3f);
Explode();
}
}
In my opinion this is Editor problem , the code looks fine.
In general I would always doubt such a statement.
I can only guess but if I understand you correctly you rather want to use the position of your object the moment when you start the Coroutine, not the one after 3 seconds:
// instead of using the current "reansform.position" rather
// use the pre-stored position passed in via parameter
public void Explode(Vector3 position)
{
Instantiate(boombEffect, position, Quaternion.identity);
Debug.Log("Transform" + transform);
Debug.Log("Position" + position);
Collider[] colliders = Physics.OverlapSphere(position, radius);
foreach(Collider rangedObject in colliders)
{
GateScript Gate = rangedObject.GetComponent<GateScript>();
Rigidbody rb = rangedObject.GetComponent<Rigidbody>();
if(Gate != null)
{
Gate.GateDestroy(explosiveDamage);
}
if(rb != null)
{
rb.AddExplosionForce(force, position, radius);
}
}
}
// If really needed you could still also have an overload
// e.g. if at some places you actually call this method from the outside
// immediately
public void Explode()
{
Explode(transform.position);
}
public IEnumerator WaitForExplode()
{
// Store the position when the routine is started
var position = transform.position;
yield return new WaitForSeconds(3f);
// Instead of using the current position rather pass in
// the previously stored one of when the routine was started
Explode(position);
}
My code spawn waves of enemies. For example, I have 3 waves. There are 5 enemies in each of them. Using coroutine I generate them. But at the same time, immediately after the generation of the first wave of enemies, the initialization of the next wave begins. But the enemies from the very first wave have not yet finished their route. Therefore, I want to add the code so that the initialization of the second wave does not start until the enemies from the first wave have disappeared from the scene.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
[SerializeField] List<WaveConfig> waveConfigs; // Here is config where i'm put a few waves ( for exmaple 3 waves with 5 enemies in each one)
[SerializeField] int startingWave = 0;
[SerializeField] bool looping = false;
IEnumerator Start() //start courutine
{
do
{
yield return StartCoroutine(SpawnAllWaves());
}
while (looping);
}
private IEnumerator SpawnAllWaves() //coururent to spawn Enemies waves one by one
{
for (int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++)
{
var currentWave = waveConfigs[waveIndex];
yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave)); //call coroutine to spawn all Enemies in wave.
}
}
private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig) //Here is spawning each Enemies in particular wave
{
for (int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
{
var newEnemy = Instantiate(
waveConfig.GetEnemyPrefab(),
waveConfig.GetWaypoints()[0].transform.position,
Quaternion.identity); //place enemy on scene
newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig); //set them pay to move on scene
yield return new WaitForSeconds(waveConfig.GetTimeBetweenSpawns()); //wait when next enemy in wave will generate
}
}
}
In your enemy have a class like e.g.
public class Enemy : MonoBehaviour
{
// Keeps track of all existing (alive) instances of this script
private static readonly HashSet<Enemy> _instances = new HashSet<Enemy>();
// return the amount of currently living instances
public static int Count => _instances.Count;
private void Awake ()
{
// Add yourself to the instances
_instances.Add(this);
}
private void OnDestroy ()
{
// on death remove yourself from the instances
_instances.Remove(this);
}
}
And then you can simply wait for
private IEnumerator SpawnAllWaves()
{
for (int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++)
{
var currentWave = waveConfigs[waveIndex];
yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave));
// After spawning all wait until all enemies are gone again
yield return new WaitUntil(() => Enemy.Count == 0);
}
}
I am trying to get the contact point between a Collider and OverlapCircleAll to play an animation on that point.
This is the method I am using for the attack.
private IEnumerator BasicAttackBehaviour()
{
canAttack = false;
Collider2D[] enemiesToDamage = Physics2D.OverlapCircleAll(attackPos, attackRange, whatIsEnemy);
for (int i = 0; i < enemiesToDamage.Length; i++)
{
enemiesToDamage[i].GetComponentInParent<Enemy>().TakeDamage(damage);
}
PlayAttackAnimation();
yield return new WaitForSeconds(attackDelay);
canAttack = true;
}
And this is the "TakenDamage" method:
public void TakeDamage(int damage)
{
health -= damage;
GameObject bloodEffect = Instantiate(takenDamageVFX, transform.position, transform.rotation);
}
I want to instantiate the VFX "bloodEffect" on the position the enemy is hitten instead of "transform.position".
You may want to switch to CircleCastAll, it will give you contact points in world space coordinates:
https://docs.unity3d.com/ScriptReference/Physics2D.CircleCastAll.html
You can set the distance parameter to 0.0f if you don't want to do a moving circle and just a single non-moving circle.
Answering to my question and thanking to the boss whom resolved my question above, here goes my fix just in case someone needs to visualise the example.
private IEnumerator BasicAttackBehaviour2()
{
canAttack = false;
RaycastHit2D[] raycastHits = Physics2D.CircleCastAll(attackPos, attackRange, directionLookingAt, 0, whatIsEnemies);
for(int i = 0; i < raycastHits.Length; i++)
{
Vector2 pointHitten = raycastHits[i].point;
raycastHits[i].collider.gameObject.GetComponent<Enemy>().TakeDamage(damage, pointHitten);
}
PlayAttackAnimation();
yield return new WaitForSeconds(attackDelay);
canAttack = true;
}
Using CircleCastAll I am able to retrieve the point of the RaycastHit2D and pass it to the enemy.
And then, I can display the blood effect on the right position of the impact.
public void TakeDamage(int damage, Vector2 pointHitten)
{
health -= damage;
GameObject bloodEffect = Instantiate(takenDamageVFX, pointHitten, transform.rotation);
}
I am developing a laser defender game.
Here is the main function responsible for spawning enemies. I have the variable waves set to 3. Then I want to destroy the position of the space ships in order to not allow them to spawn. The issue is when I start the game there are no ships at all.
if (AllMembersDead())
{
for (float i = 0; i < waves; i++)
{
SpawnUntilFull(); // number of waves to be spawned
}
Destroy(position);
}
Here is the picture of the gameobject I destroy:
Here are the function for SpawnUntilFull() and AllMembersDead() if needed.
bool AllMembersDead()
{
foreach(Transform childPositionGameObject in transform)
{
if (childPositionGameObject.childCount > 0)
{
return false;
}
}
return true;
}
void SpawnUntilFull()
{
Transform freePosition = NextFreePosition();
if (freePosition)
{
GameObject enemy = Instantiate(enemyPrefab, freePosition.transform.position, Quaternion.identity) as GameObject; // Instantiate (spawning or creating) enemy prefab (as gameobject assures that what its returning to us is no normal object but rather a game object)
enemy.transform.parent = freePosition; // the transform of whatever thing the enemy is attached to, and that would be the enemyFormation GameObject
}
if (NextFreePosition())
{
Invoke("SpawnUntilFull", spawnDelay);
}
}
I'm not sure what I'm doing wrong. Any guidance would be appreciated.
I think your AllMembersDead() function returns false because the childPositionGameObject you're looking at is the TotalPositions gameobject, hence SpawnUntilFull() is never called.
Try changing the function to point to the right parent, as such:
bool AllMembersDead()
{
foreach(Transform childPositionGameObject in position)
{
if (childPositionGameObject.childCount > 0)
{
return false;
}
}
return true;
}
I'm making a project and there is a problem that I am facing.
I have two gameObject with 2D colliders (coming from a prefab) which moves right to left. When they touch each other they deactivate.
I also have an empty game object in which i add a script Respawner which randomly generates obstacles.
The problem is when they touch each other once, they never get re-activated again.
Respawner Empty GameObject :
Border :
Prefabs :
Respawn Script:
public class Respawn : MonoBehaviour {
[SerializeField]
private GameObject[] obstacles;
private List<GameObject> listname = new List<GameObject>();
void Awake(){
InitilizeObstacle();
}
void Start() {
StartCoroutine(RandomObstacleSpawn());
}
void InitilizeObstacle(){
int index = 0;
for (int i=0; i<obstacles.Length * 3 ; i++) {
GameObject obj = Instantiate(obstacles[index],new Vector3(transform.position.x,transform.position.y,-2f),Quaternion.identity) as GameObject;
listname.Add(obj);
listname[i].SetActive(false);
index++;
if(index==obstacles.Length){
index =0;
}
}
}
void shuffle(){
for (int i=0; i<listname.Count; i++) {
GameObject temp = listname [i];
int random = Random.Range (i, listname.Count);
listname [i] = listname [random];
listname [random] = temp;
}
}
IEnumerator RandomObstacleSpawn(){
yield return new WaitForSeconds(Random.Range(1.5f,2.5f));
int index = Random.Range (0, listname.Count);
while (true) {
if(!listname[index].activeInHierarchy){
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
break;
} else {
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn());
}
}
}
Script attach to prefab for move:
public class ObstacleMove : MonoBehaviour {
private float speed = -1.25f;
void Start() { }
void Update() {
Vector3 pos = transform.position;
pos.x += speed * Time.deltaTime;
transform.position = pos;
}
}
Scripts attach to prefab for touch border:
public class BorderTouch : MonoBehaviour {
void OnTriggerEnter2D(Collider2D target){
if(target.tag=="Border"){
gameObject.SetActive(false);
}
}
}
New answer based on new question:
If you want to make things happen AFTER the collision. Put a script on Border:
using UnityEngine;
using System.Collections;
public class borderListener : MonoBehaviour {
public Respawn rS;
void OnTriggerEnter2D(Collider2D target){
rS.spawnIt ();
}
}
On Unity Editor, drag the Respawn object to the Border Script on hierarchy.
Do not skip this step or things won't work!
On Respawn script, remove the last StartCoroutine(RandomObstacleSpawn()); line on IEnumerator RandomObstacleSpawn() method. And create a public method (to access from other script) anywhere inside Respawn script:
public void spawnIt(){
StartCoroutine(RandomObstacleSpawn());
}
Old answer based on old code:
From what I see on your package:
while (true) { //A
if(!listname[index].activeInHierarchy){
//B
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
break; //C
} else {
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn()); //D
}
I am a lil noob, I will try my best to help. But this piece of code makes me wonder:
while(true) what? what is true? (EDIT: found some observation bellow)
The code seem to do this path:
Go inside the while loop (A)
Go at the first option in if statement (B)
Go to the line break; (C)
Never reaches the StartCoroutine (D) > that is why it does not activate again.
If you try and put a StartCoroutine(RandomObstacleSpawn()); before the break; you probably will get an Unity crash. What if you take off the while statement at all? You need to adjust time of yield tho.
This is the code I would use:
IEnumerator RandomObstacleSpawn(){
yield return new WaitForSeconds(Random.Range(3.5f,4.5f));
int index = Random.Range (0, listname.Count);
if(!listname[index].activeInHierarchy){
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
}else{
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn());
}
EDIT: about the while(true) I've manage to find more information about this concept here: R, How does while (TRUE) work?
But still... the break; on the code is really making the Access to StartCoroutine(RandomObstacleSpawn()); unreachable .
The problem you are seeing is fairly common, when the following code is executed gameObject.SetActive(false); it deactivate everything on that gameObject.
Thus, the next time they would have collided will never be triggered.
I'm not sure what you want to achieve for the behaviour, but if you just want to hide the gameObjects you could disable the Renderer component instead.
gameObject.GetComponent<SpriteRenderer>().enable = false;
And switch it to true when needed.
That's why we must be carefull when we use SetActive(false) on gameObject in Unity.