Own Class and List<> problems Unity C# - c#

I need your help with things that are going to blow up my mind...(Don't know why but I can't write "Hello everybody!" at the start of the post. Forum hides it:)
I can't figure out why my List <'My Tile Class> is cleared by itself.
I have few prefabs (with box-collider) with no scripts attached to them and one script on the scene. The idea is to take random prefab, attach random value to it and initiate it. After that player clicks on prefab on the scene and gets its value.
public class Generate_Field : MonoBehaviour {
public class Tiles {
public GameObject My_Tile;
public int My_Value;
}
public List<Tiles> My_List;
private GameObject EmptyObj;
// ... other variables
void Start (){
List<Tiles> My_List = new List<Tiles> ();
// ... below some calculation (about 100 rows)
//Instantiate obj and give its reference to Emptyobj
EmptyObj = Instantiate (My_GameObj_To_Inst, new Vector3(My_X, My_Y, 0f), Quaternion.identity);
Tiles Tile1 = new Tiles ();
Tile1.My_Tile = EmptyObj;
Tile1.My_Value = 1; //
My_List.Add (Tile1); // Add Tile1 (with reference to Gameobj and value to List)
// If I put code inside Void Start () it works ok and print all values
foreach (Tiles SS in My_List) {
print (SS.My_Value);
}
The problem is when I put it to Void Update ().
My_List somehow "Suicide" to zero.count although it is public List. In that case I get error:
"NullReferenceException: Object reference not set to an instance of an object..."
void Update ()
{
if (Input.GetKeyDown (KeyCode.Mouse1)) {
print (My_List.Count);
}
}

You declare a new local list in Start and populate that one. If you mean to initialise the class member, remove the type from the line doing the initialisation.
void Start (){
My_List = new List<Tiles> (); // class member, not new variable.
As a usual guideline, it's better to initialise lists at the declaration point. There's usually no reason to replace the list at a later time.

Related

How do I access a prefab and all of its instantiated clones in c# and unity?

I need a little help again please. I'm currently working on powerups and it's setup so that the powerup affectsthe prefab thats put into its prefab slot, so this is means it is currently only affecting one object (in my case, enemy). When the game starts I spawn about 20 enemies and if i pickup the powerup I want it to affect all those spawned enemies and not only the one that is dragged onto the script, does anyone have a solution or alternate method to do this please?
When instantiate them store them in a list and then iterate through all and update their value
In your case e.g.
// if enemies have a certain Component rather use that type here instead of GameObject
public GameObject enemyPrefab;
// and use the same type here instead of GameObject
private List<GameObject> enemyInstances = new List<GameObject> enemyInstances;
private void Start()
{
for(int i = 0; i < 20; i++)
{
var newEnemy = Instantiate(enemyPrefab);
enemyInstances.Add(newEnemy);
}
}
and then later
public void UpdateEnemies()
{
foreach(var enemy in enemyInstances)
{
// skip already destroyed objects
if(!enemy) continue;
// whatever to be done to the enemies
}
}
To get all game objects to an array you can use the Tag.
Just add an 'Enemy' tag to the prefab and use the code below:
public GameObject[] enemys;
void Start()
{
respawns = GameObject.FindGameObjectsWithTag("Enemy");
}
Full Unity documentation HERE

What is the best way to pass a List from a class and use it in other scripts? [duplicate]

I've searched around and I just can't get this to work. I think I just don't know the proper syntax, or just doesn't quite grasp the context.
I have a BombDrop script that holds a public int. I got this to work with public static, but Someone said that that is a really bad programming habit and that I should learn encapsulation. Here is what I wrote:
BombDrop script:
<!-- language: c# -->
public class BombDrop : MonoBehaviour {
public GameObject BombPrefab;
//Bombs that the player can drop
public int maxBombs = 1;
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space)){
if(maxBombs > 0){
DropBomb();
//telling in console current bombs
Debug.Log("maxBombs = " + maxBombs);
}
}
}
void DropBomb(){
// remove one bomb from the current maxBombs
maxBombs -= 1;
// spawn bomb prefab
Vector2 pos = transform.position;
pos.x = Mathf.Round(pos.x);
pos.y = Mathf.Round(pos.y);
Instantiate(BombPrefab, pos, Quaternion.identity);
}
}
So I want the Bomb script that's attached to the prefabgameobject Bombprefab to access the maxBombs integer in BombDrop, so that when the bomb is destroyed it adds + one to maxBombs in BombDrop.
And this is the Bomb script that needs the reference.
public class Bomb : MonoBehaviour {
// Time after which the bomb explodes
float time = 3.0f;
// Explosion Prefab
public GameObject explosion;
BoxCollider2D collider;
private BombDrop BombDropScript;
void Awake (){
BombDropScript = GetComponent<BombDrop> ();
}
void Start () {
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void OnTriggerExit2D(Collider2D other){
collider.isTrigger = false;
}
void Explode() {
// Remove Bomb from game
Destroy(gameObject);
// When bomb is destroyed add 1 to the max
// number of bombs you can drop simultaneously .
BombDropScript.maxBombs += 1;
// Spawn Explosion
Instantiate(explosion,
transform.position,
Quaternion.identity);
In the documentation it says that it should be something like
BombDropScript = otherGameObject.GetComponent<BombDrop>();
But that doesn't work. Maybe I just don't understand the syntax here. Is it suppose to say otherGameObject? Cause that doesn't do anything. I still get the error : "Object reference not set do an instance of an object" on my BombDropScript.maxBombs down in the explode()
You need to find the GameObject that contains the script Component that you plan to get a reference to. Make sure the GameObject is already in the scene, or Find will return null.
GameObject g = GameObject.Find("GameObject Name");
Then you can grab the script:
BombDrop bScript = g.GetComponent<BombDrop>();
Then you can access the variables and functions of the Script.
bScript.foo();
I just realized that I answered a very similar question the other day, check here:
Don't know how to get enemy's health
I'll expand a bit on your question since I already answered it.
What your code is doing is saying "Look within my GameObject for a BombDropScript, most of the time the script won't be attached to the same GameObject.
Also use a setter and getter for maxBombs.
public class BombDrop : MonoBehaviour
{
public void setMaxBombs(int amount)
{
maxBombs += amount;
}
public int getMaxBombs()
{
return maxBombs;
}
}
use it in start instead of awake and dont use Destroy(gameObject); you are destroying your game Object then you want something from it
void Start () {
BombDropScript =gameObject.GetComponent<BombDrop> ();
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void Explode() {
//..
//..
//at last
Destroy(gameObject);
}
if you want to access a script in another gameObject you should assign the game object via inspector and access it like that
public gameObject another;
void Start () {
BombDropScript =another.GetComponent<BombDrop> ();
}
Can Use this :
entBombDropScript.maxBombs += 1;
Before :
Destroy(gameObject);
I just want to say that you can increase the maxBombs value before Destroying the game object. it is necessary because, if you destroy game object first and then increases the value so at that time the reference of your script BombDropScript will be gone and you can not modify the value's in it.

Dynamically add a Component from a list of components

I will break it down to give an understanding.
I have a parent class Item.
I have child classes for each type of Item (Weapon, Armor, etc).
I have a list of all the items.
I'm trying to randomly grab an item from that list, and then add it as a component.
My items each randomly generate their own names, stats, etc, but they are all specific to the type of item they are and so I needed them to be generated in their own classes and not just generating "Item".
So here is what I have so far.
public Item RandomList(List<Item> list)
{
return list[random.Next(list.Count)];
}
This returns a random item from my list.
public void GenerateLoot(GameObject gameObject, List<Item> list)
{
gameObject.AddComponent(randomManager.RandomList(list));
}
This is where I'm running into problems. It says : "cannot conver from Item to System.Type". I've tried various things such as using typeof, etc.
This is something I'm fairly new to.
AddComponent seems to not support parameters other than type of a script, and if I understood you correctly, your items are all customizable in many aspects that should be applied when you add component in runtime. I advise adding method AddToGameobject in class Item:
abstract class Item : Monobehaviour {
public abstract void AddToGameObject (GameObject obj);
}
class Weapon : Item {
int damage;
int cooldown;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Weapon> ();
copy.damage = damage;
copy.cooldown = cooldown;
}
}
class Armor : Item {
int protection;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Armor> ();
copy.protection = protection;
}
}
And your loot generation function will become
public void GenerateLoot (GameObject gameObject, List <Item> list) {
randomManager.RandomList (list).AddToGameObject (gameObject);
}
If you want to attach existing component itself instead of a copy, you can't (explanations: 1, 2).
As MSL says, you need to give it a Type argument to AddComponent, as the documentation suggest:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
So my suggestion is to do it that way:
1.First you call and add the component (similar as you do)
gameObject.AddComponent(typeof(Item)) as Item;
or my prefered way:
gameObject.AddComponent<Item>();
2.Then add the item values and properties:
gameObject.GetComponent<Item>().value = item.value;
You are trying to set a GameObject Component of type Item. If you try to Add Component Item in the Inspector of any GameObject, there is none (unless your script is called Item, but that won't do what you're trying to do).
I'm assuming in your example, you may want to generate a random item upon opening a chest. There is a couple of steps to create a fully functional item, so I can't really give it all to you, so I'll give you an idea of how you'd do it.
The following will randomly change the image of the GameObject by Adding Component SpriteRenderer and randomly change the Sprite value of this Component.
public class RandomSpawn : MonoBehaviour {
GameObject gameObject;
public Sprite[] weapons; // assign an array of weapon sprites in Inspector
void Start() {
gameObject = new GameObject();
SpriteRenderer gameObjectSR = // add SpriteRenderer component
gameObject.AddComponent(typeof(SpriteRenderer)) as SpriteRenderer;
int randomIndex = Random.Range(0, weapons.Length); // random index within weapons
gameObjectSR.sprite = weapons[randomIndex]; // assign Sprite from weapons
// instantiate object
Instantiate(gameObject, transform.position, transform.rotation);
}
}
So you begin with Sprite[] weapons, an array of weapon images (basically). In the Start() function, create a new GameObject, and add a SpriteRenderer (this doesn't have to be random because you want to change what the item looks like). The random part begins with obtaining a random int (the index which is inside the length of the weapons). Then assigning a random weapons Sprite onto the GameObject, and instantiating it. This would work as compared to your attempt because SpriteRenderer is a Unity GameObject Component.
For this GameObject, you may want to attach your Weapon script (since it's derived from the Item class) and assuming you have a Weapon function SetDamage, like this:
Weapon w = gameObject.AddComponent<Weapon>();
w.SetDamage(Random.Range(5,10)); // set weapon damage between 5 and 10
This will then change the value of the Weapon damage, which can be accessed by script for when you are damaging an enemy, for example.
Note: There is probably a way to create your own components, if the normal library of components doesn't satisfy your needs.

Unity3d. Get same component for duplicated GameObject

I have a gameObject (myObject) and a component that attached somewhere in that gameObject (myComponent).
I duplicate game object:
var duplicate = Instantiate(myObject);
And then I want to have reference to the same component, but on my duplicate gameObject.
Is there a way to get same component on duplicated object?
I tried to get component by index, but is is not work for hierarchy of game objects.
You could make a copy of component but since component can have only 1 parent (gameObject) this means 1 component instance cannot be shared with 2 game objects.
Otherwise if you are OK with having 2 separate instances of component on both game objects, you could create Prefab out of gameObject (with component) and instantiate prefab.
Considering that you might have multiple components of the same type, but with different settings (properties) and you want to find component with the same settings you would have to use GetComponents and loop through results to find a new (duplicated) component with exactly the same settings.
Considering (for simplicity) you look for property called Id:
MyComponent myObjectsComponent = ... // here logic to find it, etc
GameObject duplicate = Instantiate(myObject);
List<MyComponent> myComponents = duplicate.GetComponents<MyComponent>();
// This can be replaced with bellow LINQ
MyComponent foundComponent = null;
foreach(MyComponent c in myComponents) {
if (c.Id=myObjectsComponent.Id) {
foundComponent = c;
break;
}
}
Alternatively you could use LINQ to simplify loop:
MyComponent foundComponent = (from c in myComponents where c.Id=myObjectsComponent.Id select c).FirstDefault<MyComponent>();
Thanks to all of you.
I was needed something like this:
public static T GetSameComponentForDuplicate<T>(T c, GameObject original, GameObject duplicate)
where T : Component
{
// remember hierarchy
Stack<int> path = new Stack<int>();
var g = c.gameObject;
while (!object.ReferenceEquals(g, original))
{
path.Push(g.transform.GetSiblingIndex());
g = g.transform.parent.gameObject;
}
// repeat hierarchy on duplicated object
GameObject sameGO = duplicate;
while (path.Count != 0)
{
sameGO = sameGO.transform.GetChild(path.Pop()).gameObject;
}
// get component index
var cc = c.gameObject.GetComponents<T>();
int componentIndex = -1;
for (int i = 0; i < cc.Length; i++)
{
if (object.ReferenceEquals(c, cc[i]))
{
componentIndex = i;
break;
}
}
// return component with the same index on same gameObject
return sameGO.GetComponents<T>()[componentIndex];
}
Try this:
var duplicate = Instantiate(myObject) as GameObject;
var componentDup = duplicate.GetComponent<__YOUR_COMPONENT__>();
https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html
Alright, I'm going to have a go at trying to understand what you want as well... My interpretation is that you want a reference to a specific myComponent on your duplicateObject. Very well, this is how you could do it:
public class MyObject : MonoBehaviour {
// Create a reference variable for the duplicate to have
public Component theComponent { get; set; }
void Start()
{
// Save the component you want the duplicate to have
theComponent = GetComponent<Anything>();
// Create the duplicate
var duplicate = Instantiate(gameObject);
// Set the component reference to the saved component
duplicate.GetComponent<MyObject>().theComponent = theComponent;
}
}
This way you should have a reference to the same component in all your instances of the object. You could also just create a script that holds a static reference to the specific component, which every script can reach without instantiating anything.
using UnityEngine;
public class DataHolder {
public static Component theComponent;
}

Scripts to spawn units with multiple navAgent destinations, and wait times using a Controller script in Unity

I'm trying to write a spawn controller script. Fundamentally I'm also asking to see if there's a better way to do this?
What:
Every x seconds, SpawnController.cs selects a random unit, start, PitStop and final positions from a series of arrays.
It calls 'SpawnSingle.cs' with these variables.
'SpawnSingle.cs' instantiates the GameObject, sends the destination to 'navMove' script attached to the GameObject once when created, waits for x seconds when it arrives and changes destination.
Question:
How do I make each instance of my called script (SpawnSingle)
'wait' for x seconds midway, as it's controlling the gameObject. I
can't use a coroutine to stop it.
How do I pass in the second set of coordinates after? It doesn't
seem to work when using the SpawnController.
//In SpawnController.cs
...
...
private Transform currentDestination;
private NavMove moveScript;
private GameObject newEnemy;
public static SpawnSingle newSpawn = new SpawnSingle();
void Start()
{
// To build a function to randomise the route and spawn at intervals
spawnCounter = 0;
Spawn();
spawnCounter++;
Spawn();
void Spawn()
{
newSpawn.SpawnEnemy(Enemies[spawnCounter], SpawnPoint[spawnCounter], PitStop[spawnCounter], Destination[spawnCounter]);
}
SpawnSingle.cs then assigns the first stop (pitStop) and tells it to move there using navAgents.
Problem:
When it arrives at PitStop location, I want it to wait for a few seconds, then continue on to the final destination.
This all worked OK for single instances without the Controller.
// in SpawnSingle.cs
private Transform currentDestination;
private NavMove moveScript; // This script moves the navAgent
private GameObject newEnemy;
public void SpawnEnemy(GameObject Enemies, Transform SpawnPoint, Transform PitStop, Transform Destination)
{
newEnemy = GameObject.Instantiate(Enemies, SpawnPoint.position, Quaternion.identity) as GameObject;
moveScript = newEnemy.GetComponent<NavMove>();
currentDestination = PitStop;
moveScript.destination = currentDestination;
if (arrivedAtP())
{
// Stop and wait x seconds
moveScript.nav.enabled = false;
// ***HELP HERE*** How do I make this script wait? Coroutines don't work when this script is called from an extenral source it seems?
// Wait for x seconds--
//Continue moving to final destination
//*** HELP HERE*** When instantiated from an external script, this doesn't continue to pass in the new location?***
moveScript.nav.enabled = true;
currentDestination = Destination;
moveScript.destination = currentDestination;
}
}
I think the best way to go about solving this is to make separate components for each behaviour.
Right now your spawner is responsible for 3 things:
1) making enemies, 2) setting initial nav points, 3) updating nav points later.
I would advise making your spawner only responsible for spawning objects.
Then make a navigator component that creates the nav points and pit stops etc.
so something like this:
public class Spawner : MonoBehaviour {
//only spawns, attached to some gameobject
public GameObject prefabToSpawn;
public GameObject Spawn() {
//instantiate etc..
GameObject newObject = Instantiate(prefabToSpawn);
return newObject
}
}
public class EnemyManager : MonoBehaviour {
//attached to an empty gameObject
public Spawner spawner;
public Enemy CreateNewEnemy () {
GameObject newEnemy = spawner.Spawn ();
// add it to list of enemies or something
//other stuff to do with managing enemies
}
}
public class Navigator : MonoBehaviour {
//Attached to Enemy prefab
Destination currentDestination;
public float changeDestinationTime;
void Start() {
currentDestination = //first place you want to go
InvokeRepeating ("NewDestination", changeDestinationTime, changeDestinationTime);
}
void NewDestination() {
currentDestination = // next place you want to go
}
}
Obviously that's not a complete solution but it should help get you pointed in the right direction (and change directions every 8 secs :D ). Let me know if I misunderstood what you're trying to do!

Categories

Resources