I'm Getting a error when i try to instantiate in the manager class. saying
Error CS1061: Type UnityEngine.Object' does not contain a definition forGetComponent' and no extension method GetComponent' of typeUnityEngine.Object' could be found. Are you missing an assembly reference? (CS1061) (Assembly-CSharp)
Add this to your Obstacle class:
void Start()
{
manager = GameObject.FindWithTag("ObstacleManager").GetComponent<ObstacleManager>();
}
The tag obviously has to be the tag of the gameobject the manager is attached to.
Also: Always start class names with a capital letter (I did that in the snippet, keep that in mind, you will get an error right know with that).
Maybe you want to actually change your spawning a bit though. Have two lists, one for free spawnpoints and one for occupied. When you destroy an obstacle, pass the position to the spawning function to move the position to the free list.
Edit:
Another option to create the reference is to set it in your ObstacleManager on spawning. You need to grab a reference to the instantiated obstacle for this. I believe this should work without actually grabbing the obstacle gameobject, but you could do that too.
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], pointsAvailiable[pointsIndex].position, Quaternion.identity)).GetComponent<Obstacle>();
obs.SetManagerReference(this);
And in Obstacle add
public void SetManagerReference(ObstacleManager obsManager)
{
manager = obsManager;
}
For the free position you can do something like this:
// in Obstacle.cs
public void OnMouseDown()
{
manager.SpawnNewObstacle(transform.position); // you might be able to actually pass the transform, but I'm not sure if it will get destroyed before used in the other function
Destroy(gameObject);
}
In the Manager:
public int noOfObsacles;
public float[] xPercent;
public GameObject[] TypeOfObstacles;
float y;
// to keep track of which spawn points are free and which aren't use these lists
private List<Transform> freePositions;
private List<Transform> occupiedPositions;
private void Start()
{
freePositions = new List<Transform>(spawnPoints);
occupiedPositions = new List<Transform>();
SpawnObstacles();
}
private void SpawnObstacles()
{
// just use this for initial obstacles
// call Spawn as often as needed
for(int i = 0; i < noOfObstacles; i++)
{
Spawn();
}
}
// you call this function from the obstacle that gets destroyed
public void SpawnNewObstacle(Vector3 freePos)
{
// find the spawnpoint in the occupied points
// and move it to the free ones since the obstacle got destroyed
for(int i = 0; i < occupiedPositions.Count; i++)
{
if(occupiedPositions[i].position == freePos)
{
freePositions.Add(occupiedPositions[i]);
occupiedPositions.RemoveAt(i);
break;
}
}
// and call Spawn
Spawn();
}
private void Spawn()
{
y = Random.value;
int pointsIndex = Random.Range (0, freePositions.Count);
for (int j =0; j<xPercent.Length; j++)
{
if ( y < xPercent[j])
{
// these 4 lines are essential for the spawning
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex], Quaternion.identity).GetComponent<Obstacle>();
obs.SetManagerReference(this);
occupiedPositions.Add(freePositions[pointsIndex]);
freePositions.RemoveAt(pointsIndex);
break;
}
}
}
had a bracket issue! my bad
obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex].position, Quaternion.identity)).GetComponent();
Related
Let's say I have two scripts:
SpawnManager
Enemy
In SpawnManager, I have the function SpawnEnemyWave that should instantiate 3 enemies, if the random number generator is lower than 5, then one of them should have a higher movement speed, the other shouldn't move at all.
In SpawnManager:
bool toughEnemy = true;
int waveNumber = 3;
float randomNumber = Random.Range(0, 10);
void Start()
{
SpawnEnemyWave(waveNumber);
}
void SpawnEnemyWave(int enemiesToSpawn)
{
float randomNumber = Random.Range(0, 10);
print(randomNumber);
for (int i = 0; i < enemiesToSpawn; i++)
{
if ((randomNumber < 5) && toughEnemy)
{
print("Tough");
Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
toughEnemy = false; //I make sure there is only one tough enemy per wave
}
else
{
print("Weak");
Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
}
}
}
In Enemy, I'm checking if the toughEnemy variable is set to true to modify the enemy speed before the instantiation, I put those if statements in the start function because I think than when an enemy is instantiated is when it is called.
void Start()
{
spawnManager = GameObject.Find("Spawn Manager").GetComponent<SpawnManager>();
if (spawnManager.toughEnemy)
{
speed = 1;
print("Speed " + speed);
}
else
{
speed = 0;
print("Speed " + speed);
}
}
The problem is, when the random number is 0 in the logs i see this...
random number:0
Tough (the i in the for loop is 0)
Weak (the i in the for loop is 1)
Weak (the i in the for loop is 2)
speed 0
speed 0
speed 0
And what I was expecting was something like below, because I'm modifying the variable in the SpawnManager script first before instantiating the enemy.
random number:0
Tough (the i in the for loop is 0)
speed 1
Weak (the i in the for loop is 1)
speed 0
Weak (the i in the for loop is 2)
speed 0
What am I missing here?
Timing. You’re partly correct thinking that Start will be called when the object is instantiated. But, it will be called when the next frame starts. In your current loop, you’re setting up the objects to be instantiated, then you set the toughEnemy to true. When the next frame starts, all the enemies think that a tough enemy has been set, and the output you see is correct.
If you want the manager to control the enemies, I’d personally include something like a Setup method, called by the manager. For example, in the Enemy script:
public bool isSetup { get; private set; }
public bool isTough { get; private set; }
void Setup(bool tough)
{
if ( isSetup ) return;
isSetup = true;
isTough = tough;
speed = tough ? 1 : 0;
}
Then, when you instantiate the enemy in your manager, do something like:
for (int i=0; i<enemiesToSpawn; i++)
{
var enemy = Instantiate( enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation );
var e = enemy.GetComponent<Enemy> ( );
if ((randomNumber < 5) && toughEnemy)
{
print("Tough");
toughEnemy = false; //I make sure there is only one tough enemy per wave
e.Setup(true);
}
else
{
print("Weak");
e.Setup(false);
}
}
Here’s the life cycle of a ‘frame’
Notice that the Update method processing occurs about in the middle of the frame lifecycle. You’re Instantiating 3 objects in the same Update method. THEN … the frame ends, and at the beginning of the next frame, the Start event for each of the new objects is triggered.
I would do this by putting the toughEnemy flag in the Enemy script itself. I would then add a SetTough() method to Enemy, which I would call from SpawnManager via GetComponent() after the Enemy is instantiated.
I am trying to learn how does Unity work and I now struggle with problem that I cannot access script from another script. I was searching on Interner for couple hours, I have tried many options but nothing helped.
I have 2 scripts.
CoinSpawn.cs - attached to Player (I would change it to other object but I dont know yet to which one, because its something that runs in background so it really dont need to be on player)
CollectingCoin.cs - attached to Coin (Coin is object, that its not on game scene on the start, it spawns randomly)
CoinSpawn is script that randomly spawn Instantiate of object Coin. I want to change value of CoinSpawn.currentCoinOnScreen from CollectingCoin. I ve tried
CoinSpawn test = GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
and it doesnt work. I also have my both scripts in the same asset folder. What am I doing wrong? Thank you
CoinSpawn.cs
public class CoinSpawn : MonoBehaviour
{
public GameObject coin;
public int maximumCoinPerScreen = 10;
public int currentCoinOnScreen = 0;
private int randomNumber;
private Vector2 spawnPosition;
private void Update()
{
randomNumber = Random.Range(1, 1000);
if(randomNumber >= 0 && randomNumber <= 1 && currentCoinOnScreen != maximumCoinPerScreen)
{
currentCoinOnScreen++;
float spawnY = Random.Range
(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).y, Camera.main.ScreenToWorldPoint(new Vector2(0, Screen.height)).y);
float spawnX = Random.Range
(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).x, Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, 0)).x);
spawnPosition = new Vector2(spawnX, spawnY);
GameObject coinObject = Instantiate(coin, spawnPosition, Quaternion.identity);
}
}
}
CollectingCoin.cs
public class CollectingCoin : MonoBehaviour
{
UnityEngine.UI.Text Coins;
public static int totalCoins = 0;
private void Start()
{
Coins = GameObject.Find("Score").GetComponent<UnityEngine.UI.Text>();
}
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
totalCoins++;
Destroy(gameObject);
Coins.text = "COINS: " + totalCoins.ToString();
// TESTING
CoinSpawn test = GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
CoinSpawn test2 = GetComponent<CoinSpawn>();
}
}
}
GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
Searches for a GameObject with the name CoinSpawn. Since you told us this component is rather attached to the player object it makes sense that it isn't found.
GetComponent<CoinSpawn>();
searches for a CoinSpawn component on the very same object your CollectingCoin is attached to. From your description this clearly also isn't the case.
Since you say the CoinSpawn is attached to the player then you probably rather want to get the component from
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
...
// rather get the component on the player object you collided with
CoinSpawn test = c2d.GetComponent<CoinSpawn>();
}
}
Alternatively assuming there is only one single instance of CoinSpawn in your scene anyway and not necessarily on your player you could use FindObjectOfType
CoinSpawn test = FindObjectOfType<CoinSpawn>();
First of all, Do not ever use GameObject.Find(), its very expensive as it will go through all game objects in your scene to find the object. and this not a performance wise.
There are many ways to do so.
Easyest one:
Add both script to same gameobject as component.
Make a global variable CoinSpawn inside CollectingCoin script and then use [serializedFiled] tag on top of it, by this way, you can drag and drop the reference in the editor before you start play. and you can access it the way you want.
2nd way:
Is same as first one, but instead of serializedFiled, just cache it at the beginning by using GetComponent.
Just make sure you have both scripts attached to the same gameobject.
public class CollectingCoin : MonoBehaviour
{
UnityEngine.UI.Text Coins;
public static int totalCoins = 0;
CoinSpawn coinSpawn;
private void Start()
{
coinSpawn = GetComponent<CoinSpawn>();
Coins = GameObject.Find("Score").GetComponent<UnityEngine.UI.Text>();
}
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
totalCoins++;
Destroy(gameObject);
Coins.text = "COINS: " + totalCoins.ToString();
// DO Whaterver you want with coinSpawn here
}
}
}
I want to bind the up and down arrow keys to cycle through different sprites upon being pressed. If one end is reached, it would loop back to the first sprite. I've tried using the following code:
public class PhaseChanger : MonoBehaviour
{
// saved for efficiency
[SerializeField]
public GameObject prefabMoon0;
[SerializeField]
public GameObject prefabMoon1;
[SerializeField]
public GameObject prefabMoon2;
[SerializeField]
public GameObject prefabMoon3;
[SerializeField]
public GameObject prefabMoon4;
// needed for new phase
GameObject currentPhase;
bool previousFramePhaseChangeInput = false;
/// <summary>
/// Start is called before the first frame update
/// </summary>
void Start()
{
currentPhase = Instantiate<GameObject>(prefabMoon0);
}
/// <summary>
/// Update is called once per frame
/// </summary>
void Update()
{
// change phase on up arrow or down arrow
if (Input.GetAxis("ChangePhase") > 0)
{
// only change phase on first input frame
if (!previousFramePhaseChangeInput)
{
previousFramePhaseChangeInput = true;
// Save current position and destroy current phase
Destroy(currentPhase);
// instantiate next phase
if (currentPhase = prefabMoon0)
{
currentPhase = Instantiate(prefabMoon1);
}
else if (currentPhase = prefabMoon1)
{
currentPhase = Instantiate(prefabMoon2);
}
else if (currentPhase = prefabMoon2)
{
currentPhase = Instantiate(prefabMoon3);
}
else if (currentPhase = prefabMoon3)
{
currentPhase = Instantiate(prefabMoon4);
else
{
// no phase change input
previousFramePhaseChangeInput = false;
}
}
}
}
When I attach the script to my main camera and run it, I'm able to make a single change with the up arrow, and then nothing else happens on subsequent presses.
I feel like I'm really close to making this work, but I also may being doing the whole thing inefficiently. Help would be much appreciated, thanks!
Also: I know I said sprites in my post and am sharing a script that calls on prefabs. I didn't know how to approach this using just the sprites without making a prefab for each. Is it possible to do this without separate prefabs for each sprite?
Problems
First of all you are using assignments
currentPhase = XY
where you should be using
currentPhase == XY
The reason why it still compiles is the implicit conversion operator for UnityEngine.Object -> bool. Basically your assigning equals writing
currentPhase = XY;
if(currentPhase)
It won't work like this either way because you are using Instantiate to create a new clone of a prefab which will of course have a different reference than the original prefab it was cloned from.
So even if your checks where looking like
if(currentPhase == XY)
they will ever be true.
Solution
Instead of checking for reference equality I would rather store all prefabs/instances in an array
public GameObject[] phases;
and then simply have an int index for this array so you can simply move to the next element from the array by increasing the index.
private int currentPhase;
And you can increase it and make it wrap around using e.g.
currentPhase = (currentPhase + 1) % phases.Length;
so it will always grow from 0 up to phases.Length - 1 and then start over from 0 again.
And then I don't know the exact requirements of your use case but I would suggest to rather not all the time use Instantiate and Destroy but rather have already all the objects as instances under your object and just (de)actívate them!
you could do this like e.g.
public GameObject[] phases;
private int currentPhase;
private void Awake ()
{
Init();
}
private void Update ()
{
if (Input.GetAxis("ChangePhase") > 0)
{
if (!previousFramePhaseChangeInput)
{
previousFramePhaseChangeInput = true;
NextPhase();
}
}
else
{
previousFramePhaseChangeInput = false;
}
}
// Disables all phases except the first one and sets the current index to 0
private void Init()
{
for(var i = 1; i < phases.Length; i++)
{
phases[i].SetActive(false);
}
phases[0].SetActive(true);
currentPhase = 0;
}
// Disables the current phase and enables the next one
// wraps around at the end of the array
public void NextPhase()
{
phases[currentPhase].SetActive(false);
// increase the counter and wrap around at the end of the array
currentPhase = (currentPhase + 1) % phases.Length;
phases[currentPhase].SetActive(true);
}
If you still want to Instantiate the objects because having them already in the scene is no option (for whatever reason) you could do it right before calling Init like e.g.
public GameObject[] phasePrefabs;
private GameObject[] phases;
private void Awake ()
{
var amount = phasePrefabs.Length;
phases = new GameObject [amount];
for(var i = 0; i < amount; i++)
{
phases[i] = Instantiate(phasePrefabs[i]);
}
Init();
}
Though as said I would prefer to already have them right away as this is way less error prone ;)
In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
I want an array of game objects, each game object has a simple script to move it. I want a controlling script to be able to trigger the remote script by referencing the array coordinates / game object at that point.
I am missing something basic in referencing a "global" variable - so apologies in advance. I have spent several hours now reading and trying things out.
Example questions like here Accessing a variable from another script C#
or
https://answers.unity.com/questions/42843/referencing-non-static-variables-from-another-scri.html
I believe the array should be static as there is only one set of data, however I don't care if it isnt
CreateGrid.cs
public class CreateGrid : MonoBehaviour
{
public static GameObject[,] gridArray = new GameObject[5, 5];
public GameObject gridSpace;
// Start is called before the first frame update
void Start()
{
GenerateGrid();
}
void GenerateGrid()
{
for (int x = 0; x < 5; x++)
{
for (int z = 0; z < 5; z++)
{
gridArray[x, z] = Instantiate(gridSpace, new Vector3(x, 0, z), Quaternion.identity) as GameObject;
}
}
// test grid works
// gridArray[2, 2].transform.Translate(0.0f, 3.0f, 0.0f);
}
}
test code
public class Pulse : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
for (int x = 0; x < 5; x++)
{
CreateGrid.gridArray[x, 3].GetComponent<MoveObject>().StartBounce(5.0f);
}
}
}
The grid of objects works.
However the test code errors with Object reference not set to an instance of an object from the line
CreateGrid.gridArray[x, 3].GetComponent().StartBounce(5.0f);
specifically CreateGrid.gridArray
Given that I seem to be really struggling with a concept, please explain clearly.
Thanks
Unless you don't clearly define otherwise, you don't know when the Start method of two MonoBehaviours will run.
In your case, the Pulse's Start method is called before CreateGrid's.
To fix your problem, I advise you to call CreateGrid's Generate method inside the Awake method of the class.
I often use this method to initialize members of the class not relying on other classes instances and use the Start method to initialize members needing other instances to be self-initialized.