I'm working on Unity3d project where the floor has to unfold gradually. I created a script FloorModule.cs where using coroutine the floor tiles are laying out gradually. Each next module has to unfold right after previous is completed. There for I created Spawner.cs to loop a new FloorModule.cs right after previous one is completed.
I can't seem to get my head around how to use coroutine to synchronize the mainloop (Spawner.cs) with subloop on prefab (FloorModule.cs).
Here is the link to the example
https://1drv.ms/u/s!AkVZpIE6f1GV4M5Ju7G5zPOrQcCe8w?e=QrghRT
P.S.
In given example, as loop goes forward I'm using "Reference.cs" class to change some variable values .
FloorModule.cs
public class FloorModule : MonoBehaviour
{
public float zSpacer = 0f;
public int instPrefabCount;
public Transform spawnPoint;
public int lenght = 15;
public int width = 5;
public GameObject floorTiles;
void Start()
{
spawnPoint = GetComponent<Transform>();
StartCoroutine(FwFloorDelay(spawnPoint));
}
public IEnumerator FwFloorDelay(Transform origin)
{
for (int l = 0; l < lenght; l++)
{
float xAngle = 90;
float yPos = 0;
float zPos = 0 + l;
for (int w = 0; w < width; w++)
{
int xSelection = Random.Range(0, 6);
GameObject xFloor = Instantiate(floorTiles, origin);
TileStatusNames(xFloor, l, w);
// defining positiona and angles
float xPos = w + (zSpacer * w);
xFloor.transform.localEulerAngles = new Vector3(xAngle, 0, 0);
xFloor.transform.localPosition = new Vector3(xPos, yPos, zPos);
yield return new WaitForSeconds(.05f);
}
}
Spawner.cs
public class Spawner : MonoBehaviour
{
public GameObject FloorModPrefab;
public References[] referenceScript;
void Start()
{
StartCoroutine(SpawnModules());
}
IEnumerator SpawnModules()
{
for (int i = 0; i < referenceScript.Length; i++)
{
referenceScript[i].instance =
Instantiate(FloorModPrefab, referenceScript[i].ref_spawnPoint.position, referenceScript[i].ref_spawnPoint.rotation);
referenceScript[i].ref_instFloorModCount = i + 1;
referenceScript[i].Setup();
yield return new WaitForSeconds(5f);
}
}
}
References.cs
[Serializable]
public class References
{
FloorModule prefabObjScript;
public GameObject instance;
public int ref_instFloorModCount;
public Transform ref_spawnPoint;
public int ref_Width = 5;
public int ref_Lenght = 15;
public void Setup()
{
// Get references to the components.
prefabObjScript = instance.GetComponent<FloorModule>();
// Set the player numbers to be consistent across the scripts.
prefabObjScript.instPrefabCount = ref_instFloorModCount;
prefabObjScript.spawnPoint = ref_spawnPoint;
prefabObjScript.width = ref_Width;
prefabObjScript.lenght = ref_Lenght;
}
}
I tried to use coroutines unfortunately in given context I realize it's impossible for me to resolve this task.
You can yield a coroutine from within another coroutine.
Changes to your Code
In References change public GameObject instance; to public FloorModule instance;
In Spawner change public GameObject FloorModPrefab; to public FloorModule FloorModPrefab;
Remove the code from Start of FloorModule.
Modify FwFloorDelay to
public IEnumerator FwFloorDelay(Transform origin = null)
{
if (origin == null)
{
origin = transform;
}
...
}
In SpawnModules, chain the floor delay coroutine
IEnumerator SpawnModules()
{
for (int i = 0; i < referenceScript.Length; i++)
{
...
yield return referenceScript[i].instance.FwFloorDelay();
}
}
Your goal should not be to synchronize separate coroutines, but rather to get the code running sequentially in one coroutine.
For example, you could make References.Setup() asynchronous, and then have it invoke FloorModel.FwFloorDelay directly instead of the FloorModel starting its own separate coroutine.
Related
I am currently programming a game in which an infinite procedural city is generated. so far everything works but because it leads to laggs if there are too many objects in the scene I wanted to make a script in which objects only appear near the player. I watched this video for help:https://www.youtube.com/watch?v=xlSkYjiE-Ck. When I tried to link this to my script (GenerateBuilding script) this error came:ArgumentException:
An item with the same key has already been added. Key: (0.0, 1.0)
System.Collections.Generic.Dictionary...
I need help to make the script work in which the houses are generated as well as the planes do, they should only be showed when the player is nearby
---Relevant Lines---
(Endless City)
calling updateChunk function in update()(updateChunk/building function is in GenerateBuilding script)
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
adding to dictionary line 68-80(UpdateVisibleChunks function)
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
EndlessCity, CityChunk class
CityChunk function, sending position to GenerateBuilding script to instantiate buildings in right position.
building.requestBuildingSquad(positionV3);
GenerateBuilding relevant lines
builderH function, instantiates the buildings
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
AddTest function, adds instantiates objects from builderH to a dictionary
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
Testing class, testing function, adds objects to class
public Testing(GameObject obj)
{
MeshObject = obj;
}
that should be all relevant lines
full scripts(really similar)
EndlessCity Script(this scripts generates the planes and gives position for GenerateBuilding script)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class EndlessCity : MonoBehaviour
{
public const float maxViewDst = 10;
public Transform viewer;
private GenerateBuilding building;
public static Vector2 viewerPosition;
int chunkSize;
int chunksVisibleInViewDst;
Dictionary<Vector2, CityChunk> terrainChunkDictionary = new Dictionary<Vector2, CityChunk>();
List<CityChunk> terrainChunksVisibleLastUpdate = new List<CityChunk>();
void Start()
{
chunkSize = 8 - 1;
chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
}
void Update()
{
viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
UpdateVisibleChunks();
}
void UpdateVisibleChunks()
{
building = FindObjectOfType<GenerateBuilding>();
building.UpdateBuildings();
for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
{
terrainChunksVisibleLastUpdate[i].SetVisible(false);
}
terrainChunksVisibleLastUpdate.Clear();
int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);
for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
{
for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
{
Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
{
terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
if (terrainChunkDictionary[viewedChunkCoord].IsVisible())
{
terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
}
}
else
{
terrainChunkDictionary.Add(viewedChunkCoord, new CityChunk(viewedChunkCoord, chunkSize, transform));
}
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
}
}
}
public class CityChunk
{
private GenerateBuilding building;
public GameObject meshObject;
public Vector3 positionV3;
Vector2 position;
Bounds bounds;
public CityChunk(Vector2 coord, int size, Transform parent)
{
building = FindObjectOfType<GenerateBuilding>();
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
positionV3 = new Vector3(position.x, 0, position.y);
int xPosition = building.xLength / 2;
int zPosition = building.zLength / 2;
float xOfsset = building.xOfsset;
float zOfsset = building.zOfsset;
float spaceBetween = building.spaceBetween;
//Instantiate plane
meshObject = Instantiate(building.groundObject, positionV3 + new Vector3((xPosition + xOfsset) * spaceBetween, -.5f, (xPosition + 1 + zOfsset) * spaceBetween), Quaternion.identity);
SetVisible(false);
building.requestBuildingSquad(positionV3);
}
public void UpdateTerrainChunk()
{
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
public void SetVisible(bool visible)
{
meshObject.SetActive(visible);
}
public bool IsVisible()
{
return meshObject.activeSelf;
}
}
}
GenerateBuilding(this script generates Buildings on the planes)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBuilding : MonoBehaviour
{
public int minHeight = 2;
public int maxHeight = 8;
public int cubeTileX;
public int cubeTileZ;
public int xLength;
public int zLength;
public float spaceBetween;
public float xOfsset;
public float zOfsset;
public GameObject TesObject;
public GameObject[] Base;
public GameObject[] secondB;
public GameObject[] roof;
public GameObject groundObject;
public List<GameObject> objectsss;
public Dictionary<Vector2, Testing> cityChunkDictionary = new Dictionary<Vector2, Testing>();
public List<Testing> buildingObjects = new List<Testing>();
public GameObject Test;
void Start()
{
//requestBuildingSquad(this.transform.position);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//
}
}
public void requestBuildingSquad(Vector3 position)
{
//*getting the middle of the city squad
int xPosition = xLength / 2;
int zPosition = zLength / 2;
//*
for (int z = 0; z < zLength; z++)
{
zOfsset++;
for (int x = 0; x < xLength; x++)
{
GenerateBuildings(position);
}
xOfsset = 0;
}
zOfsset = 0;
}
public void GenerateBuildings(Vector3 position)
{
int bHeight = Random.Range(minHeight, maxHeight);
float bOfsset = 0;
bOfsset += builderH(Base, bOfsset, position);
for (int i = 0; i < bHeight; i++)
{
bOfsset += builderH(secondB, bOfsset, position);
}
bOfsset += builderH(roof, bOfsset, position);
xOfsset++;
}
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
public class Testing
{
public GameObject MeshObject;
Vector2 position;
Bounds bounds;
public Testing(GameObject obj)
{
MeshObject = obj;
}
public void SetVisible(bool visiblee)
{
MeshObject.SetActive(visiblee);
}
public bool IsVisible()
{
return MeshObject.activeSelf;
}
public void UpdateCityChunk(Vector3 viewerPosition, Vector2 coord, int size, float maxViewDst)
{
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
}
}
The problem is that you are trying to add twice elements with the same key.
here is the documentation of the Add method for dictionaries, and as it states, trying to add an existing key throws an error.
You can either use the TryAdd method, which adds an item only if the key doesn't exist already in the dictionary, or update the value with the existing key as you can see here.
i am trying to develop a unity game which uses prefabs spawn at random locations from an array. The problem i am having is prefabs are spawning on the top of each other too many times. I have tried to prevent this from happening with the help of other topics not only here but also from google but i couldnt apply some methods to my code. So my goal is keep tracking last spawned object position and spawn next object at diffrent position from the array i have created within the obstacle script. Is there anyone who can help me?
This is my obstacle scripts which attached to prefabs.
public class obstacle : MonoBehaviour
{
private Rigidbody targetRb;
// Start is called before the first frame update
private float minSpeed = 12;
private float maxSpeed = 16;
private float ySpawnPos = 6;
private float NewPosition = -1.87f;
private List<Vector3> spawnPositions = new List<Vector3>();
private int index;
public int offset = 1;
void Start()
{
index = Random.Range(0, spawns.Length);
transform.position = spawns[index];
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
}
Vector3 RandomForce()
{
return Vector3.down * Random.Range(minSpeed, maxSpeed);
}
Vector3[] spawns = new[]
//spawns = new Vector3[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
}
This is my game manager script which spawn the prefabs.
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
//spawnera alternatif daha güzel
{
public List<GameObject> targets;
// Start is called before the first frame update
public int score;
public TextMeshProUGUI scoreText;
public TextMeshProUGUI gameOverText;
public Button restartButton;
public bool isGameActive;
public float spawnRate = 3.0f;
public Text highScore;
public Text highestScore;
void Start()
{
isGameActive = true;
score = 0;
UpdateScore(0);
StartCoroutine(SpawnTarget());
highScore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]);
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
// Update is called once per frame
public void UpdateScore(int scoreToAdd)
{
score += scoreToAdd;
scoreText.text = "Score: " + score;
}
public void GameOver()
{
restartButton.gameObject.SetActive(true);
gameOverText.gameObject.SetActive(true);
isGameActive = false;
Time.timeScale = 0;
highScore.gameObject.SetActive(true);
highestScore.gameObject.SetActive(true);
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
Time.timeScale = 1;
}
}
A stack or queue might be a bit overkill, so instead, I'll just keep an iterator and check if we are exceeding the bounds of your array. I am also going to move the initial placement to the GameManager so it can manage which positions are left in the spawn list locations.
// move the array of locations to the GameManager
Vector3[] spawns = new[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
private int currentSpawnItr = 0;
private int maxSpawnItr = 0;
private System.Random rndm;
private void Start()
{
rndm = new System.Random();
maxSpawnItr = spawns.Length;
ShufflePositions();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
GameObject tmpObstacle = Instantiate(targets[index]);
tmpObstacle.transform.position = GetNextPosition();
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
private Vector3 GetNextPosition()
{
// when we reach the bounds of our spawn array length, shuffle it again
// and reset our iterator
if(currentSpawnItr == maxSpawnItr)
{
currentSpawnItr = 0;
ShufflePositions();
}
++currentSpawnItr;
return spawns[currentSpawnItr-1];
}
private void ShufflePositions()
{
Vector3 prevLast = spawns[maxSpawnItr-1];
spawns = spawns.OrderBy(spawn => rndm.Next()).ToArray();
// when our new first spawn was our old last spawn
if(spawns[0] == prevLast)
{
// randomly pick an index to swap our first element to
int randomIdx = rndm.Next(1, maxSpawnItr);
spawns[0] = spawns[randomIdx];
spawns[randomIdx] = prevLast;
}
}
I have not tested the above snippet. It is more the direction you could take this. If you have issues implementing this let me know.
I am trying to implement a waves of enemies, where you need to create a new wave when the current wave has been destroyed.
Wave Code
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
WaveConfig waveConfig;
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
GameObject newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(),
waveConfig.GetEnemyWaiponts()[0].transform.position,
Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
}
If I understand your point correctly.
this code not help you to wait previous wave was eliminate, it work flow is spawn next wave instantly after previous wave spawn completed.
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IF you need to wait until current wave eliminate.
When wave begin, you should track all enemies in the wave (eg. amount, alive data, or something) and check it for start next wave.
Psudo :
void Update(){
if(enemyInWave == 0){ // Or Condition is fullfill to start next wave
NextWave();
}else {
// Do Nothing
}
}
void NextWave(){
var waveConfig = WaveConfig[currentWaveIndex]
enemyInWave = waveConfig.monsters.Amount
SpanwEnemy(waveConfig.monsters)
}
or something like that
You could use a static HashSet/List where you store all instances of created enemies. The enemies simply add and remove themselves to/from the HastSet during their lifetime.
Then you can simply wait until no enemies are left before going into the next wave.
Something like
public class YourWaveClass : MonoBehaviour
{
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
private static HastSet<Enemy> currentlyAliveEnemies = new HashSet<Enemy>();
public static void Add(Enemy enemy)
{
if(!currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Add(enemy);
}
public static void Remove(Enemy enemy)
{
if(currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Remove(enemy);
}
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
// NOTE: It would be better/saver if you make your prefab directly of type Enemy then
var newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(), waveConfig.GetEnemyWaiponts()[0].transform.position, Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
// Now wait until the HashSet is empty => all enemies are dead
yield return new WaitUntil(() => currentlyAliveEnemies.Count == 0);
}
}
So what you need to have on your enemy prefabs would be a component like
public class Enemy : MonoBehaviour
{
// This is automatically called during Instantiate
private void Awake()
{
YourWaveClass.Add(this);
}
// this is called automatically when your object gets destroyed/dies
private void OnDestroy()
{
YourWaveClass.Remove(this);
}
}
I created a custom class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Obstacle {
public GameObject gameObj;
public Color color;
public GameObject starExplosion;
public GameObject regular_Trail;
[HideInInspector]
public bool firstCollistion = true;
public static Vector3 SpawnLocation()
{
int positionQuadran = Random.Range(1, 3);
switch (positionQuadran)
{
//spawn above the player
case 1:
return new Vector3(Random.Range(1.5f, -1.5f),
Random.Range(4f - SpawnStars.closerToPlayer, 4.5f),
Random.Range(1, -3.2f));
//spawn benith the player
case 2:
return new Vector3(Random.Range(1.5f, -1.5f),
Random.Range(-0.5f, SpawnStars.closerToPlayer),
Random.Range(1f, -3.2f));
}
return Vector3.zero;
}
}
Now, as you can see in this class there is a variable public GameObject gameObj; Now in another script I need to acces the instance of the Obstacle class that this gameObj instance is in. And I am trying to do that like this:
private void OnCollisionEnter(Collision collision)
{
collision.collider. //what do I do next?
}
For a few reasons I don't want to make the Obstacle class inherit from MonoBehaviour. Since I can't access this class from another GameObject that would be in the scene how do I access it by only knowing the gameObj variable?
Update I will add the script that I use to generate the Obstacle Class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnStars : MonoBehaviour
{
[SerializeField]
private List<Obstacle> obstacles;
[HideInInspector]
public List<Obstacle> normalStarsPool = new List<Obstacle>();
[SerializeField]
private List<Obstacle> otherObstacles;
[HideInInspector]
public List<Obstacle> otherObstaclesPool;
[SerializeField]
private int spawnNumber = 2;
private Obstacle nextObstacleToSpawn;
[SerializeField]
public GameObject panel;
public static bool spawnNow = true;
public static bool first_find_star = true;
public static float starSpeed;
/* this variables will make the stars to spawn closer and closer to the player as
the score is progresing*/
public static float closerToPlayer;
private float spawnPositionZ;
private void Start()
{
first_find_star = true;
spawnNow = true;
GeneratePrefabs(spawnNumber, obstacles, normalStarsPool);
StartCoroutine(ShuffleList(normalStarsPool));
GeneratePrefabs(2, otherObstacles, otherObstaclesPool);
StartCoroutine(ShuffleList(otherObstaclesPool));
}
private void LateUpdate()
{
if (spawnNow)
{
spawnNow = false;
if (first_find_star)
{
nextObstacleToSpawn = FindStar(normalStarsPool);
first_find_star = false;
}
//spawn the current star
int randomNumber = Random.Range(0, 100);
if(randomNumber >= 20){
nextObstacleToSpawn = FindStar(normalStarsPool);
}
else{
Debug.Log("corrupt star");
nextObstacleToSpawn = FindStar(otherObstacles);
}
SpawnStar(nextObstacleToSpawn);
}
}
void GeneratePrefabs(int how_many, List<Obstacle> prefabList, List<Obstacle> poolList)
{
foreach (Obstacle prefab in prefabList)
{
for (int i = 0; i <= how_many; i++)
{
Obstacle go = new Obstacle
{
gameObj = Instantiate(prefab.gameObj)
};
go.regular_Trail = go.gameObj.transform.GetChild(1).gameObject;
go.starExplosion = go.gameObj.transform.GetChild(0).gameObject;
go.color = prefab.color;
go.gameObj.SetActive(false);
//setap all the colors for the obstacle
ParticleSystem ps = go.starExplosion.GetComponent<ParticleSystem>();
ParticleSystem.MainModule psmain = ps.main;
psmain.startColor = go.color;
//setup the collor of a partycle system
ps = go.starExplosion.GetComponent<ParticleSystem>();
psmain = ps.main;
psmain.startColor = go.color;
psmain.startColor = go.color;
go.gameObj.GetComponent<Renderer>().material.color = go.color;
poolList.Add(go);
}
}
}
Obstacle FindStar(List<Obstacle> poolList)
{
while (true)
{
int randomIndex = Random.Range(0, poolList.Count);
if (!poolList[randomIndex].gameObj.activeInHierarchy)
{
Color color = poolList[randomIndex].color;
color.a = 0.5f;
panel.GetComponent<Renderer>().material.color = color;
return poolList[randomIndex];
}
else randomIndex = Random.Range(0, poolList.Count);
}
}
void SpawnStar(Obstacle star)
{
star.firstCollistion = false;
star.starExplosion.SetActive(false);
star.regular_Trail.SetActive(true);
star.gameObj.GetComponent<MeshRenderer>().enabled = true;
ScaleDifficulty();
star.gameObj.transform.position = Obstacle.SpawnLocation();
star.gameObj.SetActive(true);
}
//Shuffle a list every 4 seconds, don't pus this in an update or something cuz it's a coroutine
IEnumerator ShuffleList(List<Obstacle> list_to_Shuffle)
{
while (true)
{
yield return new WaitForSeconds(4f);
for (int i = 0; i < list_to_Shuffle.Count; i++)
{
Obstacle temp = list_to_Shuffle[i];
int randomIndex = Random.Range(i, list_to_Shuffle.Count);
list_to_Shuffle[i] = list_to_Shuffle[randomIndex];
list_to_Shuffle[randomIndex] = temp;
}
}
}
//this will scale the difficulty as the score get's higher and higher
public void ScaleDifficulty()
{
if (Menu.score < 60)
{
//the speed of the star as the score goes up
starSpeed = ((float)Menu.score / 30) + 1f;
//how close relative to the player will the stars spawn in the x and y axis?
closerToPlayer += Menu.score / 60;
// Debug.Log(starSpeed);
}
}
}
As you can see i generated the Obstacle objects in this method:
void GeneratePrefabs(int how_many, List<Obstacle> prefabList, List<Obstacle> poolList)
{
foreach (Obstacle prefab in prefabList)
{
for (int i = 0; i <= how_many; i++)
{
Obstacle go = new Obstacle
{
gameObj = Instantiate(prefab.gameObj)
};
go.regular_Trail = go.gameObj.transform.GetChild(1).gameObject;
go.starExplosion = go.gameObj.transform.GetChild(0).gameObject;
go.color = prefab.color;
go.gameObj.SetActive(false);
//setap all the colors for the obstacle
ParticleSystem ps = go.starExplosion.GetComponent<ParticleSystem>();
ParticleSystem.MainModule psmain = ps.main;
psmain.startColor = go.color;
//setup the collor of a partycle system
ps = go.starExplosion.GetComponent<ParticleSystem>();
psmain = ps.main;
psmain.startColor = go.color;
psmain.startColor = go.color;
go.gameObj.GetComponent<Renderer>().material.color = go.color;
poolList.Add(go);
}
}
}
In short, you can't. Not the way you've got things set up. If you need to access that object globally, you'll need to keep track of it globally. Unity provides a way to do that (essentially the Service Locator pattern), but you've said you don't want to use it. So you're going to have to build your own.
Assuming you've got a collection of your Obstacle objects, e.g. IList<Obstacle> obstacles you could get it by obstacles.Where(o => o.gameObj == myLocalGameObjReference). A better solution, assuming you have this script attached 1:1 for each Obstacle would be to simple inject the Obstacle into the script. Look at a dependency injection (DI) framework like Zenject to assist with configuring and managing your dependencies. Another alternative is the singleton pattern. Personally, I'd opt for DI over singleton. The singleton seems easier at first, but you'll quickly run into trouble with it.
Now in another script I need to acces the instance of the Obstacle class that this gameObj instance is in.
Given an instance of an Obstacle class, how can you even know that it is referenced by an instance of GameObject at all? Or how can you know that it's referenced by only one GameObject?
Unless there's a two-way reference - the GameObject has a reference to the Obstacle and the Obstacle also has a reference back to the GameObject - there's no way to determine what references an instance of Obstacle.
I have a class "Bullet" which I instantiate using a method CreateBullet(), since there are going to be multiple bullets i decided that I should make bullet an array, though this didn't work out and I've spent an hour on trying to fix it.
What I call in my Initialize method:
Bullet bullet[] = Bullet.CreateBullet[1]();
The Bullet class:
class Bullet
{
public float2 position;
public float angle { get; set; }
public float speed { get; set; }
public static Bullet CreateBullet()
{
Bullet bullet = new Bullet()
{
position = new float2()
};
return bullet;
}
public void Move()
{
}
}
Could you please show me what's wrong with the code? Thank you in advance.
With this, you create an array of 5 bullets:
Bullet[] bullets = new Bullet[5];
And then you need to fill the array by creating a bullet for each array entry:
for (int i = 0; i < bullets.Length; i++)
{
bullets[i] = Bullet.CreateBullet();
}
You can wrap this logic in a function:
public Bullet[] CreateBullets(int amount)
{
Bullet[] bullets = new Bullet[amount];
for (int i = 0; i < bullets.Length; i++)
{
bullets[i] = Bullet.CreateBullet();
}
return bullets;
}
And then you can use a function to initialize the array:
public void Test()
{
Bullet[] bullets = CreateBullets(5);
}
You could do something like this, not quite what you where trying to achieve, but it might inspire you a bit more
Usage
// Create your bullets
var bullets = new List<Bullet>();
// Create a raw/empty bullet with default properties
var newBullet1 = new Bullet();
// Create bullet with some initialized properties
var newBullet2 = new Bullet()
{
Angle = 35,
Position = 0,
Speed = 200
};
bullets.Add(newBullet1);
bullets.Add(newBullet2);
Something extra for fun
// Move all your bullets at once
foreach (var bullet in bullets)
{
bullet.Move();
}