Context
Hello, currently creating a clone of "Crossy Road" and what I'm trying to do is to spawn my moving object called "Vehicle" at a random speed and rate of spawn. This is also applicable to "Plank", but I will start first with the vehicle. So far, everything is working fine as intended for the game mechanics, but I would like to finalize with this issue so it is fully functional in terms of playability.
Problem
My issue now is I 3 different spawns objects: grass, river, and road. Each object holds other objects (let's call it spawners) depending of what field is being spawn. For example, if grass field object is spawned, it will spawn trees depending in a random varied selection. Another example is with road field. When the road is spawned, a vehicle will be spawned from either left or right in its current initial position. This vehicle will moves as intended with a random speed, but not with the original spawn position and rate (as shown in the GIF. The vehicle spawns in the middle of the road and not in the beginning of the left/right road).
As far I'm aware, my rate is currently unused because it is not the main issue I want to solve. However, the issue now is with the transform position not working as I have pictured in my head. So what is happening is that when the road is spawned again, the vehicle is spawned in the middle of the trajectory instead of resetting to the beginning.
Also, I have noticed that when I print the vehicle object, the Z-axis has a weird number compared to the original position.
Attempts done
I have been thinking that maybe it is the way I have set everything up. I have 4 vehicle objects with a child object called "Tank". However, in each vehicle object, I'm using SetActive(...) only and not really reusing the object itself to the beginning. Later on, I want to organize this spaghetti code and optimize it (e.g ObjectPool to spawn my roads and other GameObjects after hitting a certain range, adding a player range detection to spawn a field to name a few).
To be honest, my whole code feels bloated for something simple. This will be fixed once everything is working accordingly.
Code (DISCLAIMER: there is the possibility that there are unused variables)
SpawnManager.cs (some links provided too from learning to make this)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
** Weighted randomness: https://forum.unity.com/threads/random-numbers-with-a-weighted-chance.442190/
** Scriptable Object Weight spawn example: https://www.youtube.com/watch?v=FCksj9ofUgI&ab_channel=LlamAcademy
** From scratch loot tables with Scriptable Objects to make a loot table: https://www.youtube.com/watch?v=tX3RWsVLnzM&ab_channel=GregDevStuff
** Creating a random with an animation curve: https://www.youtube.com/watch?v=zw1OERK5xvU&ab_channel=HamzaHerbou
** Random Vehicle position spawn (maybe this can help me): https://stackoverflow.com/questions/51312481/move-spawn-object-to-random-position
*/
public class SpawnManager : MonoBehaviour
{
public GameObject Player;
public Spawn[] Field;
public GameObject[] SpawnObjectTrees;
public GameObject[] SpawnObjectVehicles; //different vehicles
public GameObject[] SpawnObjectPlanks; //3 sizes (small, medium, large)
private PlayerControl2 playerControlScript;
private int distancePlayer;
private int toggle;
private bool keepSpawning;
bool vehicleFlag = false;
bool plankFlag = false;
public float randomNumSpawn;
void Awake()
{
keepSpawning = true;
playerControlScript = GameObject.Find("PlayerObject").GetComponent<PlayerControl2>();
InvokeRepeating("Spawner", 3f, randomNumSpawn);
}
void Update()
{
if (Input.GetButtonDown("up") && !playerControlScript.gameOver)
SpawnField();
}
void Spawner()
{
bool activeLeft = false;
bool activeRight = false;
if (vehicleFlag)
{
print(initialObjectSpawn);
for (int i = 0; i < SpawnObjectVehicles.Length; i++)
{
print($"{SpawnObjectVehicles[i]}: {SpawnObjectVehicles[i].transform.position}"); //Here I get the weird position.z values pretty wonky
toggle = Random.Range(0, 2);
if (toggle == 1 && !activeLeft)
{
activeLeft = true;
SpawnObjectVehicles[i].SetActive(true);
}
if (toggle == 0 && !activeRight)
{
activeRight = true;
SpawnObjectVehicles[i].SetActive(true);
}
else
SpawnObjectVehicles[i].SetActive(false);
}
}
}
void SpawnField()
{
//I want to spawn the vehicles, planks, and trees in sets accordingly to the field (grass, river, road)
//For vehicles and planks, they can move horizontally from either -z or z boundaries
//NOTE: keepSpawning may be useless if i have a playerControlScript.gameOver already in here
if (keepSpawning)
{
distancePlayer += 3;
Vector3 intPos = new Vector3(0, 0, 0);
int i = Random.Range(0, 1000);
for (int j = 0; j < Field.Length; j++)
{
if (i >= Field[j].minProbabilityRange && i <= Field[j].maxProbabilityRange)
{
intPos = new Vector3(distancePlayer, -1f, 0);
GameObject Surface = Instantiate(Field[j].spawnField);
if (Surface.CompareTag("Grass"))
TreeToggle();
if (Surface.CompareTag("Road"))
{
vehicleFlag = true;
VehicleToggle();
}
// if (Surface.CompareTag("River")) this will be the same as vehicle
// {
// plankFlag = true;
// PlankToggle();
// }
//Add spawn for vehicles and planks with given spawnrate/spawn intervals
Surface.transform.position = intPos;
vehicleFlag = false;
plankFlag = false;
}
}
}
}
void TreeToggle()
{
int counter = 0;
for (int i = 0; i < SpawnObjectTrees.Length; i++)
{
int toggle = Random.Range(0, 2); //[0, 2)
if (toggle == 1 && counter < 5) //True and when there are already 5-4 trees to toggle
{
counter++;
SpawnObjectTrees[i].SetActive(true);
}
else //fills the rest to inactive Trees
SpawnObjectTrees[i].SetActive(false);
}
}
void VehicleToggle()
{
// I have Left and Right with 2 vehicles in each. My goal is to setActive one of them each side at a time with a different interval spawnrate and speed
Spawner();
}
void PlankToggle()
{
Spawner();
}
}
[System.Serializable]
public class Spawn
{
public GameObject spawnField;
public float minProbabilityRange = 0.0f;
public float maxProbabilityRange = 0.0f;
}
Hierarchy/Inspector
If there is any information you want to know, feel free to ask and I will make a quick edit to fulfill these goals. Again, thank you for your time and appreciate it :D I hope you are having a good day!
Related
This is a follow-up for a different question I posted earlier. The solution found there led to a new problem I will describe here.
Having objects spawn, with random size within set range, with equal distance between them regardless of framerate
I am trying to create the effect of several buildings in different sizes moving from left to right at a constant speed regardless of framerate. I want the spacing between the buildings to be the same no matter the size, and each building's size is randomized within a range.
I solved this by comparing the size of the last building with the current and dividing it by their speed, which worked initially, but was very heavy on performance due to Instantiating prefabs constantly. This is the original code from my ScrollingCity script.
if(Time.time > nextBuilding)
{
spawningRatio = ((randomSize / 2) + (buildingPrefab.transform.localScale.x / 2) + distanceBetween) / (speed);
nextBuilding = Time.time + spawningRatio;
buildingPrefab.transform.localScale = new Vector3(randomSize, randomSize, randomSize);
Instantiate(buildingPrefab, spawnPoint.transform.position, Quaternion.identity);
randomSize = Random.Range(ranMin, ranMax);
}
In the linked thread I was advised to use pooling for my objects, to reduce the load, and I was able to make it work with the use of some tutorials. However, I now don't know how to compare the sizes of the last to objects pulled from the pool, to ensure that they all have the same spacing between them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
#region Singleton
public static ObjectPooler Instance;
private void Awake()
{
Instance = this;
}
#endregion
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
public GameObject SpawnFromPool (string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning("Tag doesn't exist");
return null;
}
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
objectToSpawn.SetActive(true);
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
}
}
This is the script for my Object Pooler, learned from a tutorial.
I then call the function in my ScrollingCity Script, where I try to get the transform value of the latest object created so I can compare them with the current object, but I can only get the transform of the first object pulled, until it reaches the end of the Queue.
if(Time.time > nextBuilding)
{
nextBuilding = Time.time + spawningRatio;
objectPooler.SpawnFromPool("Build", transform.position, Quaternion.identity);
lastBuilding = objectPooler.SpawnFromPool("Build", transform.position, Quaternion.identity);
Debug.Log(lastBuilding.transform.localScale);
}
And it only returns the transform of the first object in the log:
So my Question is how can I access the scale of the last object in the Queue, and compare it with the current to control the frequency with which they are created, and effectively keep the distance between each object the same regardless of scale?
I am making a game and I have a world generated with Perlin-Noise. I spawn trees on a random 'grass' location and then I want to be able to give the tree health so I can chop it.
else if(noise <= 0.43)//gras
{
int treeNumber = Random.Range(0,100);
if(treeNumber <5)
{
GameObject makeTree = Instantiate(tree[0], pos, Quaternion.identity);
}
}
I have a tree sprite wit a script component
public static int treesHealth;
// Start is called before the first frame update
void Start()
{
treesHealth = 100;
}
I have a player with a 'fight' script
public void DoDamage(int amount, int startTreeHealt)
{
Vector3 origin = playerpos;
int range = 1;
RaycastHit2D[] hit = Physics2D.RaycastAll(origin,dir,range);//startpos, direction, range
for(int i = 0; i < hit.Length; i++)
{
if(hit[i].transform.gameObject.layer == LayerMask.NameToLayer("nature"))
{
Vector3 hitpos = hit[i].transform.position;
treeHealth.treesHealth = treeHealth.treesHealth - amount;
if(treeHealth.treesHealth < 1)
{
Destroy(hit[i].transform.gameObject);
GameObject makeTrunks = Instantiate(treeTrunks, hitpos, Quaternion.identity);
}
return;
}
}
At first it seems to work well but there are 2 scenarios where it goes wrong. when I hit a tree a couple of times (not enough to 'chop it') and walk to another tree.
In the time I walk to the other tree I go 'out of screen' and new trees are instantiated.
The already hit trees health gets reset it is back to 100
In the time I walk to the other tree I don't go 'out of screen' and there aren't new trees instantiated
The 2nd, and all other trees have the same health as the already hit tree
I want to be able to hit the trees independently. The trees have to 'remember' how much health they have left.
I'm totally lost on how to achieve this but I think that the script attached to the tree isn't the way I can do this.
I hope someone can help me in the right direction thank you for your time and help.
I am creating a game, with-in the game there's 7 GameObject Prefabs, each one Instantiate it's on level, I am now creating the main script that will instantiate each GameObject Prefab, now what I want to do, is so create each 80f "seconds" to instantiate a new GameObject Prefab, and destroy the last one, for example, in 0f seconds since Level Started, Instantiate (Random) GameObject Prefab, and in 80f seconds destroy the one I created in 0f seconds, and Instantiate a new one, and so on.
This is the script that I have right now, it doesn't work, it does Instantiate new gameobject, but do not destroy the last one I created.
I hope you can help me / give me an idea how to solve this problem
NOTE I tried using Stack, but when I destroy Prefab from the stack, it destroyes the prefab it self, and it's not recoverable.my script:
TimeSinceLevelStarted = Time.timeSinceLevelLoad;
if (Mathf.Clamp(TimeSinceLevelStarted - TimeToLoadNext, -2f, 2f) == TimeSinceLevelStarted - TimeToLoadNext
&& LoadedFirstLevel == false)
{
GameObject go = LevelsPrefabs[Random.Range(0, LevelsPrefabs.Length)];
FirstLevel(go);
LoadedFirstLevel = false;
Debug.Log("Instantiated Prefab2");
}
}
private void FirstLevel(GameObject go)
{
if (LoadedFirstLevel == false)
{
Instantiate(go, new Vector3(0, 0, 0), Quaternion.identity);
goStack.Push(go);
Debug.Log("Instantiated Prefab1");
TimeToLoadNext += 50f;
LoadedFirstLevel = false;
if (TimeToLoadNext >= 30f) {
Destroy(go);
}
}
}
Repeating events are best done with Coroutines. Also Unity tells us to avoid instantiating and destroying GameObjects when the game runs (since its "slow") - especially if they might get reused.
So a quick and dirty implementation of your desired behaviour should be the following. If you want to make sure that an object doesnt get actived twice in succession, you cant take the straight forward random approach, but youll need to shuffle the array and spawn it in the new order. There are plenty of examples for shuffling an array available. The below should be easily adaptable with a bit of tinkering on your side ;)
public class RandomSpawner : MonoBehaviour {
//i learned there should always be an option to break out of a loop
public bool exitCoroutine = false;
public GameObject[] prefabs;
public float waitTime = 80.0f;
WaitForSeconds waitObject;
GameObject[] gameObjects;
int currentlyActive = 0;
void Start() {
waitObject = new WaitForSeconds(waitTime);
gameObjects = new GameObject[prefabs.Length];
for (int i = 0; i < prefabs.Length; i++) {
gameObjects[i] = Instantiate(prefabs[i]);
gameObjects[i].SetActive(false);
}
StartCoroutine(SpawnRandomObject());
}
void ActivateRandom() {
int nextActive = Random.Range(0, gameObjects.Length);
gameObjects[currentlyActive].SetActive(false);
gameObjects[nextActive].SetActive(true);
currentlyActive = nextActive;
}
IEnumerator SpawnRandomObject() {
while (!exitCoroutine) {
ActivateRandom();
yield return waitObject;
}
}
}
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
https://docs.unity3d.com/ScriptReference/WaitForSeconds.html
I have game for multiple players where each user selects their hero before game starts and that loads the selected heroes into the battle arena.
I have small issue with getting the instantiation to spawn in correct numbers of players
The method that I have for Spawning the characters:
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
This script checks for amount of selected hero cards and puts it against my list that has all the available heroes to choose from(prefabs).
The debug.log shows that only the correct heroes get called.
Instantiate ends up spawning a loot of heroes instead of the selected amount.
For clarity I attach full class:
{
private int playerSize; //amount of choices for card selection
private GameManager GM;
[Header("Lists for Spawning in Heroes")]
public List<GameObject> SelectedCards;
public List<GameObject> AvailableHeroes;
public List<Transform> PlayerSpawnLocation;
[Header("Canvas used for the game")]
public Transform GameCanvas;
public Transform CharacterCanvas;
//When scene starts it takes how many players will be picking a card.
void Start()
{
//connects this script with gamenmanager to be able to manipulate the cameras
GM = GameObject.Find("GameManager").GetComponent<GameManager>();
//gets playersize information from main menu selection
PlayerPrefs.GetInt("PlayerSize");
playerSize = PlayerPrefs.GetInt("PlayerSize");
SelectedCards = new List<GameObject>();
//enables/disables correct canvas not to cause any problems when we initiate this scene
GameCanvas.gameObject.SetActive(false);
CharacterCanvas.gameObject.SetActive(true);
}
// Update is called once per frame
void Update()
{
if (playerSize <= 0)
{
Placement();
GM.CharacterSelectionCamera.enabled = false;
GameCanvas.gameObject.SetActive(true);
CharacterCanvas.gameObject.SetActive(false);
GM.BattleCamera.enabled = true;
}
}
public void PlayerSelected(int cardPicked)
{
playerSize -= cardPicked;
}
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
}
I hope someone can explain where I am going wrong with this.
Thanks,
I got the answer, I guess I was just being tired from working and could not see the obvious.
For those who wonder what solution is
The method gets called each frame thus it continues to endlessly spawn objects
There are 2 ways to fix it
1 Make coroutine and then return after you make your initial batch
2 Use a boolean at update so not only it checks player size but also whenever it can spawn it or not, you set the boolean to false after method get called.
I did not even notice the update function part.
Just a heads up, in your start function, PlayerPrefs.GetInt("PlayerSize"); is not doing anything since the value is not saved anywhere.
I am trying to make a game which "kind of" simulates the shooting of the "worms" game. The player can choose the position (circular) of an object and then, the force that is applied to the object should move in the direction its pointing towards. I tried using the AddForce(transform.right) code, but it would just go to the right. (2D BoxCollider and RigidBody2D)
Then comes the hard part, making the player choose the force by charging the power. When the player holds down the "f" key, I want the power to go up to a certain point. Once it reaches that point, I want it to go down again and then up again, so the player can choose the power he wants. I have no idea how to go about this.
It's been awhile since I've did Unity coding, so there may be some minor errors with my syntax but it should give you an idea of how to accomplish this. Your best bet for the loop is to use a coroutine to not block the main thread.
in Update() check for 'on key down' for F and start this coroutine:
IEnumerator Cycle()
{
float max = 10.0f;
float min = 1.0f;
float interval = 0.5f;
do
{
for(int i=min;i<max;i++)
{
PowerValue = i;
yield return new waitforseconds(interval);
if(!input.getkey(f))
break;
}
for(int i=max;i>min;i--)
{
PowerValue = i;
yield return new waitforseconds(interval);
if(!input.getkey(f))
break;
}
} while(input.getkey(f));
}
And back in update() use that powerValue with getKeyUp(f)
And here is PowerValue setup as a parameter that prevents code from setting the max and min outside of a 1 to 10 range (configurable)
private float powerValue = 1.0f;
public float PowerValue
{
get { return powerValue; }
set {
if(value>10f)
powerValue=10f;
else if (value<1f)
powerValue=1f;
else
powerValue=value;
}
}