I want to make a script that loops through all game objects with the tag, Enemy
I modified a code snippet from here, and ended up with 2 errors.
Cannot Implicitly convert 'Unity.GameObject[]' to 'Unity.GameObject'
and
Error CS1579 foreach statement cannot operate on variables of type 'GameObject' because 'GameObject' does not contain a public instance definition for 'GetEnumerator'
If anyone could tell me why this is happening or a solution to this I would be very grateful, thanks in advance!
Here is my code:
void FixedUpdate()
{
GameObject objects = GameObject.FindGameObjectsWithTag("Enemy");
var objectCount = objects.Length;
foreach (var obj in objects)
{
// Move the players accordingly
//var rb =
Vector2 direction = (player.position - transform.position).normalized;
obj.rigidbody.velocity = direction * moveSpeed;
}
}
FindGameObjectsWithTag as the name hints returns a GameObject[].
In order tog et the attached Rigidbody2D use GetComponent<Rigidbody2D>
It should be either GameObject[] or simply var
/*GameObject[]*/ var objects = GameObject.FindGameObjectsWithTag("Enemy");
var objectCount = objects.Length;
foreach (var obj in objects)
{
// Move the players accordingly
var rb = obj.GetComponent<Rigidbody2D>()
Vector2 direction = (player.position - transform.position).normalized;
rb.velocity = direction * moveSpeed;
}
The second one was just a follow up error since you declared objects as a GameObject which indeed as the error says has no GetEnumerator implementation.
In general it is not the best thing to use FindObjectsWithTag repeatedly. I would rather use a pattern with a static list of all existing instances like
// Put this component on your enemy prefabs / objects
public class EnemyController : MonoBehaviour
{
// every instance registers to and removes itself from here
private static readonly HashSet<EnemyController> _instances = new HashSet<EnemyController>();
// Readonly property, I would return a new HashSet so nobody on the outside can alter the content
public static HashSet<EnemyController> Instances => new HashSet<EnemyController>(_instances);
// If possible already drag the Rigidbody into this slot via the Inspector!
[SerializedField] private Rigidbody2D rb;
// public read-only access
public Rigidbody2D Rb => rb;
private void Awake()
{
if(!rb) rb = GetComponent<Rigidbody2D>();
_instances.Add(this);
}
private void OnDestroy()
{
_instances.Remove(this);
}
}
and then use
var enemies = EnemyControler.Instances;
foreach (var enemy in enemies)
{
// Move the players accordingly
Vector2 direction = (player.position - transform.position).normalized;
enemy.Rb.velocity = direction * moveSpeed;
}
Change GameObject type declaration to var
Or change it to GameObject[] since FindGameObjectsWithTag returns an array of GameObject
Also I wouldnt use FindGameObjectsWithTag its slow. More so in a update method
First off, change GameObject to GameObject[] because FindGameObjectsWithTag returns a GameObject array. Be careful not to confuse it with FindGameObjectWithTag without the s which returns a single GameObject
Secondly, I think it would be best to have a separate Enemy script or something like that and assign them to each enemy rather than using FindGameObjectsWithTag which is very slow.
Related
im kind of newbie to unity and object oriented programming. Recently im trying to clone Cube Surfer mobile game. Basic idea from my view is this ;
-When we triggered to collactable cubes which consist script will be duplicated and it will be belong the main cube parent as child object then triggered cube will be destroyed.(After positioning)
-Later this duplicate child objects(cubes) will do the same when they enter trigger area of other collectable cubes(those will be the same prefab but did not yet create a prefab)
Im trying to collect(create a clone of it position and destroy the object) cubes. For first cube, I added some code to my movement script which is below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
public GameObject addup;
Rigidbody rb;
float controlSpeed = 1.25f;
float forwardMovementSpeed = 10f;
private Vector3 axisGet;
float deathTime;
public int collected;
// Start is called before the first frame update
void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
collected = 0;
}
// Update is called once per frame
void FixedUpdate()
{
axisGet = new Vector3(0, 0, Input.GetAxis("Horizontal"));
rb.MovePosition(transform.position + Vector3.left * forwardMovementSpeed * Time.deltaTime + axisGet * controlSpeed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if(other.tag=="add up")
{
gameObject.transform.position += Vector3.up;
var newObject = Instantiate(addup.gameObject, Vector3.zero, Quaternion.identity);
newObject.transform.parent = transform;
newObject.transform.position = gameObject.transform.position + Vector3.down;
Destroy(other.gameObject);
newObject.GetComponent<BoxCollider>().isTrigger = false;
collected++;
}
}
}
WORKED WITHOUT ERROR BUT THEN, I applied the same method to collectable cubes scripts.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UpNdown : MonoBehaviour
{
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "add up")
{
GameObject parentTransform;//?????????
parentTransform = gameObject.GetComponentInParent<GameObject>(); //get first cube component
parentTransform.transform.position += Vector3.up; //first cube one unit up
GameObject newObject; // ?????????
newObject = Instantiate(other.gameObject, Vector3.zero, Quaternion.identity) as GameObject; //???????????
Debug.Log(newObject);
var collect = parentTransform.GetComponent<Movement>().collected;
if (other != null)
{
Destroy(other.gameObject); //destroy triggered collactable
}
newObject.transform.parent = parentTransform.transform; //setting parent to new cube
newObject.transform.position = parentTransform.transform.position + Vector3.down * (collect + 1); //setting position of new cube
newObject.GetComponent<BoxCollider>().isTrigger = false; //prepare the below cubes(new cubes) for trigger with other collactable cubes
collect++;
}
}
}
And, I had nullexception error in every line in ontriggerenter method then, I changed(added) the lines with question marks. So, I get
ArgumentException: GetComponent requires that the requested component 'GameObject' derives from MonoBehaviour or Component or is an interface.
UnityEngine.GameObject.GetComponentInParent[T] (System.Boolean includeInactive) (at :0)
UnityEngine.GameObject.GetComponentInParent[T] () (at :0)
UpNdown.OnTriggerEnter (UnityEngine.Collider other)
I thought, I understood the OOP instance idea which objects in the scenes are instances scripts has their own value... but i dont understand that while I was operating on a instance why it is null in the memory :((((((((((((( if PC can't access how instantiates the object ?
SORRY I WRITE THIS LONG BUT IM ABOUT THE EDGE AGAIN I DON'T WANT TO QUIT BECAUSE OF FACING THIS PROBLEM AGAIN
TY FOR YOUR ANSWERS, ALREADY APPRECIATED :)
GameObject is no component (it is rather a container of all components attached to it!)
=> you can't get it using GetComponent or GetComponentInParent at all. (Btw Note that GetComponentInParent starts the search on this object itself first before bubling up the hierarchy so either way this isn't what you want to use).
What you want is simply transform.parent to get the Transform component of the parent object of the object this script is attached to (assuming the rest of your code does what it should)
private void OnTriggerEnter(Collider other)
{
// Rather use CompareTag instead of string ==
// The latter silently fails in case of typos making your debugging life miserabel
// it is also slightly less efficient
if (!other.CompareTag("add up")) return;
// Get the parent of this object
var parentTransform = transform.parent;
// Cache this you will need it later see below
var parentMovement = parentTransform.GetComponent<Movement>();
var collect = parentMovement.collected;
parentTransform.position += Vector3.up;
// By using the correct type you want later you can skip GetComponent
var newObjectCollider = Instantiate(other, Vector3.zero, Quaternion.identity);
Debug.Log(newObjectCollider);
Destroy(other.gameObject);
newObjectCollider.transform.parent = parentTransform;
newObjectCollider.transform.position = parentTransform.position + Vector3.down * (collect + 1);
newObjectCollider.isTrigger = false;
// This does absolutely nothing. Numeric values are passed by value and there is no connection
// between your local variable and the component you got it from
//collect++;
// you probably rather want to increase
parentMovement.collected++;
}
Or alternatively since you anyway have a specific component on your parent object you could also do
// Instead directly get this component
var parentMovement = GetComponentInParent<Movement>();
// Then wherever needed access the transform through it
var parentTransform = parentMovement.transform;
...
I'm quite sure though that the other way round it is more efficient since you already know exactly which parent you are searching the component on.
Or - and this would probably be the best option - cache that parent information once right away:
// If possible already reference this vis the Inspector
[SerializeField] private Movement parentMovement;
private Transform parentTransform;
private void Awake ()
{
if(! parentMovement) parentMovement = GetComponentInParent<Movement>();
parentTransform = parentMovement.transform;
}
Ty sir my first code was nearly the same of your first answer but didn't work again at least for error.
private Transform parentTransform;
private void Awake ()
{
if(! parentMovement) parentMovement = GetComponentInParent<Movement>();
parentTransform = parentMovement.transform;
}
But this worked, I guess the problem I need to define instances to class so they don't disappear instantly on the trigger function or direct I need to define them to class.
Anyway, thank you derHugo now need to solve different problems :D
I have a script that moves instantiated game objects depending on their tag. However, the game objects that have been instantiated are not moving.
Instantiation Script:
void Update()
{
tillNextSpawn += Time.deltaTime;
Debug.Log(tillNextSpawn);
if (tillNextSpawn >= 2)
{
UnityEngine.Debug.Log("Instantiating circle");
screenPosition = Camera.main.ScreenToWorldPoint(new Vector3(Random.Range(0, Screen.width), Random.Range(0, Screen.height), Camera.main.farClipPlane / 2));
Instantiate(circle, screenPosition, Quaternion.identity);
tillNextSpawn = 0.0f;
}
}
Enemy controller script(moves the enemies)
void FixedUpdate()
{
/*GameObject[]*/
var objects = GameObject.FindGameObjectsWithTag("Enemy");
var objectCount = objects.Length;
foreach (var obj in objects)
{
// Move the players accordingly
var rb = obj.GetComponent<Rigidbody2D>();
Debug.Log(rb);
Vector2 direction = (player.position - obj.transform.position).normalized;
rb.velocity = direction * moveSpeed;
}
}
You should be able to that, and the issue maybe somewhere else in your project.
I created a sample project that recreates what you're trying to do, trying to be as similar as I could be to the sample code you send, you can find it here:
https://github.com/webocs/unity-so-sample-tags
For what I can see, your console is sending an exception
get_main is not allowed to be called...
What comes to my mind, is that that exception is breaking the entire execution, and that's why nothing is happening.
As a side note, I don't know your project so I don't really know why you're building it this way. Said that, why aren't you creating an Enemy script that's attached to the enemy prefab? If you have many enemies you're going to be finding and iterating through all of them in each update tic. If you create an Enemy script and attach it to the prefab you should be able to handle the movement of the enemy using the transform of the gameObject that the script is attached to. This way each enemy is a standalone entity.
I hope all of this helps!
Edit:
I've edited the repo and added a scene called "IndividualEnemies" that illustrates what I told you in the comments
If you want the enemy to follow the player, try doing the following:
//Attach this script to the enemy
Transform player;
private Rigidbody2D rb;
private Vector2 movement;
public float moveSpeed;
void Awake()
{
player = ScriptNameOnPlayer.instance.gameObject.transform;
}
// Start is called before the first frame update
void Start()
{
rb = this.GetComponent<Rigidbody2D>();
}
void Update()
{
Vector3 direction = player.position - transform.position;
direction.Normalize();
movement = direction;
}
void FixedUpdate()
{
moveCharacter(movement);
}
void moveCharacter(Vector2 direction)
{
rb.MovePosition((Vector2)transform.position + (direction * moveSpeed * Time.deltaTime));
}
//But make sure your player script has this line of code:
public static ScriptNameOnPlayer instance;
Hope this helps !
I'm using Unity and C# and I am fairly new to both.
I have a class called Pathfinding attached to a empty gameobject. It requires a link to a start gameobject and an end gameobject and makes a list of nodes between the two in the shortest path. (A*).
What I need help with is:
How do I instantiate my enemy gameobject from a prefab and link it to this script (usually done by dragging the gameobject onto the respective tile in the editor).
How can I make a gameobject move by following the list created by the pathfinding algorithm (maybe takes the first one of the list, moves, then recalculates)? More info: My board is divided into many small cubes, the algorithm uses these to create a path. The path (list of these nodes) is stored. I want multiple enemies to
Thank you very much. Ask me if its not clear enough and I'll try to provide screenshots or info. :)
1.
Instantiate enemy and add it to a list (which is in your script):
using UnityEngine;
public class Controller: MonoBehaviour
{
GameObject enemyPrefab;
List<GameObject> enemiesList = new List<GameObject>();
void Start()
{
GameObject enemy = Instantiate(enemiePrefab, transform.position, transform.rotation);
enemiesList.Add(enemy);
}
}
2.
Make the object move on your pathfinding:
There are many ways to do that, but I suggest you to store in a List, array or whatever you want, the different waypoints that you have.
Then make your enemies follow one waypoint after another.
So now your main script should look like:
using UnityEngine;
public class Controller : MonoBehaviour
{
public GameObject enemyPrefab;
private List<GameObject> enemiesList = new List<GameObject>();
public List<GameObject> wayPoints = new List<GameObject>();
void Start()
{
GameObject enemy = Instantiate(enemiePrefab, transform.position, transform.rotation);
enemy.wayPoints = wayPoints;
enemiesList.Add(enemy);
}
}
And your enemy script should look like:
using UnityEngine;
public class Enemy : MonoBehaviour
{
public List<GameObject> wayPoints = new List<GameObject>();
public float speed;
private Transform target;
int waypointIndex = 0;
private void Start()
{
target = List[waypointIndex].transform;
waypointIndex++;
}
void Update()
{
// The step size is equal to speed times frame time.
float step = speed * Time.deltaTime;
// Move our position a step closer to the target.
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
//If we arrive to the target, get the new target
if(this.transform.position == target.position)
{
waypointIndex++;
//if it is the last element, go to the first one again
if(wayPointIndex > List.Count())
{
wayPointIndex = 0;
}
target = List[waypointIndex].transform;
}
}
}
Note that you can alterate the code so your controller also moves every enemy, but that's up to you!
I'll keep this brief and a bit short, but I currently have a particle system that seems to not be rendering even though my collision works.
I have trouble understanding other peoples work so I have not been able to find a solution I can understand.
Here is my code:
public float speed;
public Rigidbody rb;
public int health;
private float knockback;
private float knockup;
public ParticleSystem Eparticle; //*** variable for particle system ***
// Use this for initialization
void Start()
{
rb = GetComponent <Rigidbody>();
knockback = 100f;
knockup = 250f;
}
void OnCollisionEnter(Collision col)
{
if (col.gameObject.name == "enemy")
{
health = health - 20;
rb.AddRelativeForce(Vector3.back * knockback);
rb.AddRelativeForce(Vector3.up * knockup);
Destroy(col.gameObject);
Instantiate(Eparticle);
}
if (col.gameObject.name == "endgoal")
{
SceneManager.LoadScene("level 1");
}
}
What am I doing wrong with my instantiate(Eparticle) line?
Could someone please talk me through a solution?
Thank You :)
You should invoke the Instantiate method at the position where you want the particle prefab to appear.
You could do something like this...
Instantiate(Eparticle,transform.position,transform.rotation);
Actually, you can also create (instantiate) a GameObject at runtime as follows...
GameObject obj= Instantiate(Eparticle,transform.position,transfrom.rotation) as GameObject;
This way, you have some sort of 'control' over the instantiated gameobject.
For instance, you can destroy the object after using it by calling the Destroy() method.
E.g.:
Destroy(obj,2f);//Destroys the created object after 2 seconds.
Of course, this is not a good way to go about it if you are going to be instantiating and destroying a lot of objects. You should read about Object Pooling for this purpose.
This may seem like a stupid question but I'm stuck with it. I have GameObjects in a list (List<GameObject>) and I want to add them on the scene runtime, prefarbly on predefined places (like placeholders or something). What would be a good way to do it? I've been searching the net but can't really find anything that would solve this. This is my code so far:
public static List<GameObject> imglist = new List<GameObject>();
private Vector3 newposition;
public static GameObject firstGO;
public GameObject frame1;//added line
void Start (){
newposition = transform.position;
firstGO = GameObject.Find ("pic1");
frame1 = GameObject.Find ("Placeholder1");//added line
//this happens when a button is pressed
imglist.Add(firstGO);
foreach(GameObject gos in imglist ){
if(gos != null){
print("List: " + gos.name);
try{
//Vector3 temp = new Vector3 (0f, 0f, -5f);
Vector3 temp = new Vector3( frame1.transform.position.x, frame1.transform.position.y, -1f);//added line
newposition = temp;
gos.transform.position += newposition;
print ("position: " + gos.transform.position);
}catch(System.NullReferenceException e){}
}
}
}
How can I place the pics (5) on the predefined spots?
//----------------
EDIT: Now I can place 1 image to a placeholder (transparent png). For some reason z-value goes all over the place so it needs to be forced to -1f but that's OK. I add the images to the list from other scenes and there can be 1-5 of them. Do I need to put the placeholders in another list or array? I'm a bit lost here.
If you've already created 5 new objects you can just do like they do here:
http://unity3d.com/learn/tutorials/modules/beginner/scripting/invoke under the InvokeScript
foreach(GameObject gos in imglist)
{
Instantiate(gos, new Vector3(0, 2, 0), Quaternion.identity);
}
I don't really understand what you're trying to do, but if I'm correct and you have a list of objects, and you know where you want to move them at runtime, just make two lists,
one containing the objects and
one containing transforms of empty game-objects in the scene placed at those predefined positions, and match them at runtime.
Populate both lists from the inspector.
public List<GameObject> imglist = new List<GameObject>();
public List<Transform> imgPositions = new List<Transform>();
void Start()
{
for(var i = 0 i < imglist.Count; ++i)
{
imglist[i].transform.position = imgPositions[i].position
}
}
The general best way is to create prefabs for your objects, passing them as a parameter and instantiate when needed (Start in your case). That's the common case, but maybe yours is slightly different.
This is an example of passing a prefabs array and to instantiate one object for each one in the array:
public GameObject prefabs[];
List<GameObject> objects = new List<GameObject>();
void Start() {
for(GameObject prefab in prefabs) {
GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity) as GameObject; // Replace Vector3.zero by actual position
objects.Add(go); // Store objects to access them later: total enemies count, restart game, etc.
}
}
In case you need several instances for the same prefab (multiple enemies or items, for instance) just adapt code above.