I am making a game like 2 cars. And I have written code to create a instantiater. It is a car game and there are 4 lanes. Let me show you a picture of my game and yeah this is solely for practise.
Player should avoid square objects and eat circle objects but sometimes 2 square objects get spawned in a same lane making impossible for player to win. I have written this script to control that but I failed. Please help me. At least have a check to my DetectSameLaneFunction(). And tell me what I am doing wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour {
public GameObject[] objects;
public float delaytime = 2f; // this is separate for each prefab with which script is attaches
public float spawnrate = 1f; // this is separate for each prefab with which script is attaches
public static int lastgameobjectindex;
public static GameObject lastgameobject;
public static GameObject SecondLastGameObject;
private float loadingtime;
private GameObject go; // just a temporary variable
public static List<GameObject> spawnobjects = new List<GameObject>();
// Use this for initialization
void Start () {
loadingtime = delaytime;
}
// Update is called once per frame
void Update () {
if (Time.time > loadingtime)
{
float randomness = spawnrate * Time.deltaTime;
if ( randomness < Random.value)
{
Spawners();
}
NextLoadTime();
}
}
private void Spawners()
{
int spawnnumber = Random.Range(0, 2);
GameObject go = Instantiate(objects[spawnnumber]) as GameObject;
go.transform.position = this.transform.position;
spawnobjects.Add(go);
Debug.Log(spawnobjects[spawnobjects.Count-1]);
DetectSameLaneObjects();
/* if (spawnobjects.Count > 4)
{
spawnobjects.RemoveAt(0);
}*/
}
private void DetectSameLaneObjects()
{
if (spawnobjects.Count > 3)
{
lastgameobject = spawnobjects[spawnobjects.Count - 1];
SecondLastGameObject = spawnobjects[spawnobjects.Count - 2];
lastgameobjectindex = spawnobjects.Count - 1;
if (SecondLastGameObject.transform.position.x != lastgameobject.transform.position.x
)
{
if (Mathf.Abs(lastgameobject.transform.position.x- SecondLastGameObject.transform.position.x) < 2.3f)
{
Debug.Log("Destroy function getting called");
Destroy(spawnobjects[lastgameobjectindex]);
spawnobjects.RemoveAt(lastgameobjectindex);
}
}
}
}
void OnDrawGizmos()
{
Gizmos.DrawWireSphere(this.transform.position, 0.6f);
}
void NextLoadTime()
{
loadingtime = Time.time + delaytime;
}
}
Related
It's working fine in the Start :
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
[Header("Objects To Move")]
public Transform objectToMovePrefab;
public int numberOfObjectsToMove = 1;
[Header("Delay At Start")]
public bool useDelay = false;
public bool randomDelayTime = false;
public float delayTime = 3f;
[Header("Movement Speed")]
public float speed = 3f;
public bool randomSpeed = false;
//with this approach, you use GameObjects to represent your waypoints
//(they can be empty if you want the waypoint to be invisible)
[Header("Waypoints")]
[SerializeField] private List<Transform> waypoints;
private List<WaypointsFollower> waypointsFollowers;
[Header("LineRenderer")]
public LineRenderer lineRenderer;
private bool useLineRenderer = true;
private List<Vector3> lineRendererPositions;
[SerializeField] int lineRendererNumOfPositions;
private void Start()
{
for (int i = 0; i < numberOfObjectsToMove; i++)
{
var parent = GameObject.Find("Moving Object Parent");
var objectToMove = Instantiate(objectToMovePrefab, parent.transform);
objectToMove.name = "Platfrom";
}
foreach (GameObject mn in GameObject.FindGameObjectsWithTag("Moving Object"))
{
waypointsFollowers.Add(mn.GetComponent<WaypointsFollower>());
}
StartCoroutine(SendObjectToMove());
}
public int Count => lineRendererPositions.Count;
public Vector3 GetWaypoint(int index)
{
return lineRendererPositions[index];
}
private void Update()
{
if (useLineRenderer && lineRenderer.positionCount > 0 && CurvedLineRenderer.linesSet)
{
lineRendererPositions = GetLinePointsInWorldSpace();
lineRendererNumOfPositions = GetLinePointsInWorldSpace().Count;
foreach(Transform waypoint in waypoints)
{
lineRendererPositions.Add(waypoint.position);
}
useLineRenderer = false;
}
}
private IEnumerator SendObjectToMove()
{
foreach (GameObject mn in GameObject.FindGameObjectsWithTag("Moving Object"))
{
WaypointsFollower waypointsFollower = mn.GetComponent<WaypointsFollower>();
if (useDelay)
{
if (randomDelayTime)
{
yield return new WaitForSeconds(Random.Range(1, 5));
}
else
{
yield return new WaitForSeconds(delayTime);
}
}
if (randomSpeed)
{
waypointsFollower.speed = Random.Range(1, 100);
}
else
{
waypointsFollower.speed = speed;
}
if (waypoints.Count > 0 || lineRendererPositions.Count > 0)
{
waypointsFollower.go = true;
}
else
{
waypointsFollower.go = false;
}
}
}
List<Vector3> GetLinePointsInWorldSpace()
{
var pointsToMove = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(pointsToMove);
//the points returned are in world space
return pointsToMove.ToList();
}
}
Then on each moving object cloned prefab I added this script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaypointsFollower : MonoBehaviour
{
[SerializeField] private Waypoints waypoints;
public float speed = 5f;
[SerializeField] private float waypointDistanceThreshold = 0.1f;
[SerializeField] private bool goBack = false;
public bool go = false;
private int waypointIndex = 0;
private void Start()
{
waypoints = GameObject.Find("Waypoints").GetComponent<Waypoints>();
}
void Update()
{
if (go && waypoints.Count > 0)
{
Vector3 waypoint = waypoints.GetWaypoint(waypointIndex);
//movement
float distance = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, waypoint, distance);
//check if we've reached the waypoint
float threshold = waypointDistanceThreshold; //how close is considered having reached the waypoint
if (Vector3.Distance(transform.position, waypoint) < threshold)
{
//wraps back to 0 when we reach last waypoint
if (goBack)
{
waypointIndex = (waypointIndex + 1) % waypoints.Count;
}
else
{
if (waypointIndex != waypoints.Count - 1)
waypointIndex = waypointIndex + 1;
}
}
}
}
}
I can control the speed of each Follower individual but I also want to control the speed of all the Followers from the main Waypoints script and for now I can change the speed in the Inspector and it will affect the changes only in the Start() when starting the game.
How can I make that it will change the speed of all the Followers also in the Update at run time ?
I created a List of all the Followers :
foreach (GameObject mn in GameObject.FindGameObjectsWithTag("Moving Object"))
{
waypointsFollowers.Add(mn.GetComponent<WaypointsFollower>());
}
but not sure how to apply the speed changes in the Update ? Looping in the Update over the Followers each frame is too expensive I guess.
I am not completely sure what you would like to achieve. Well, I know you want to increase the variable Speed on a list of objects that have the component WaypointsFollower(). I suppose what I am confused about is when you want to do this. If Update() is too frequent, what determines when the speed should change?
In your original code, you are using the GameObject.FindGameObjectsWithTag("Moving Object")) too frequently. Only do this once in Awake() or Start(). If any new objects are instantiated, then dynamically add them to the list. Using the FindGameObjectsWithTag frequently can get very expensive. What I am referring to is that you are already storing them, but then in the method SendObjectToMove() you are using it again. Is there a reason you need to call it again in that IEnumartor?
And if you are instantiating these objects on this line
for (int i = 0; i < numberOfObjectsToMove; i++)
{
var parent = GameObject.Find("Moving Object Parent");
var objectToMove = Instantiate(objectToMovePrefab, parent.transform);
objectToMove.name = "Platfrom";
}
Then just store the reference after instantiating them instead of using the FindGameObjectsWithTag.
Here is a very condensed version of your code.
private List<WaypointsFollower> waypointsFollowers;
private void Start()
{
foreach (GameObject mn in GameObject.FindGameObjectsWithTag("Moving Object"))
{
waypointsFollowers.Add(mn.GetComponent<WaypointsFollower>());
}
StartCoroutine(SendObjectToMove());
}
private IEnumerator SendObjectToMove()
{
foreach (WaypointsFollower follwer in waypointsFollowers)
{
// continue your code, but use follower instead of the other object
// you are already storing these objects, so why not reuse them?
}
}
private void UpdateSpeed(float speed)
{
foreach (WaypointsFollower follwer in waypointsFollowers)
{
follower.speed = speed
}
}
I am only using the FindGameObjectsWithTag once in Start() then reusing the list waypointsFollowers it is adding to. I added a function UpdateSpeed that will take a float as a parameter and update every object in the list.
I currently write a game, and I have 2 scripts that generate the ground in the game. However, instead of generating them as the player comes to the end of one ground, they're generated as soon as the game starts. I don't want this to happen.
Does someone know why it happens?
Please help me fix this.
Thanks!
This is my code:
Script 1:
public class ObjectPooler : MonoBehaviour
{
public GameObject pooledObject;
public int pooledamnt;
List<GameObject> pooledObjects;
// Start is called before the first frame update
void Start()
{
pooledObjects = new List<GameObject>();
for (int i = 0; i < pooledamnt; i++)
{
GameObject obj = (GameObject)Instantiate(pooledObject);
obj.SetActive(false);
pooledObjects.Add(obj);
}
}
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObjects.Count; i++)
{
if (pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
GameObject obj = (GameObject)Instantiate(pooledObject);
obj.SetActive(false);
pooledObjects.Add(obj);
return obj;
}
// Update is called once per frame
void Update()
{
}
}
Script 2:
public class
GroundGenerator : MonoBehaviour
{
public GameObject thePlatform;
public Transform GenOnPoint;
public float DistanceBetween;
private float PlatformWidth;
public float DistanceBewtweenmin;
public float Di stanceBetweenmax;
public ObjectPooler objectpool;
public GameObject[] thePlatforms;
private int platformSelecter;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
GameObject newPlatform = objectpool.GetPooledObject();
newPlatform.transform.position = transform.position;
newPlatform.transform.rotation = transform.rotation;
newPlatform.SetActive(true);
}
}
Here is a basic script to generate ground as the player moves :
using UnityEngine;
public class GroundGeneration : MonoBehaviour
{
public float ClosestDistacnceFromPlayer;
public GameObject GroundTile;
public float TileWidth;
public Transform Player;
void Start()
{
//Spawn a tile so that the player won't fall off at the start
SpawnTile(1);
}
void SpawnTile(int n)
{
int i = 0;
//Spawn n tiles
while (i < n)
{
Instantiate(GroundTile, transform.position, Quaternion.identity);
i++;
//Teleport the "Ground generator" to the end of the tile spawned
transform.position += TileWidth * Vector3.right; // Or use Vector3.forward if you want to generate ground on z axis.
}
}
void FixedUpdate()
{
if (Vector3.Distance(Player.position, transform.position) <= ClosestDistacnceFromPlayer)
{
SpawnTile(1);
}
}
}
The GroundTile should have its origin at the point where it meets with the previous tile and it should be a prefab.
The Ground will start generating at the position of the object containing the ground generation script.
Why is this connecting all the health bars of my enemies together, even though their actual health is decreasing at its specified rate?
public class FillHealth : MonoBehaviour
{
Image HealthBar;
private NormalMonster normalMonster;
// Start is called before the first frame update
void Start()
{
HealthBar = GetComponent<Image>();
normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
}
// Update is called once per frame
void Update()
{
UpdateHealthLeft();
}
public void UpdateHealthLeft()
{
if (normalMonster.healthLeft > 0)
{
HealthBar.fillAmount = normalMonster.healthLeft / normalMonster.setHealth;
}
}
}
This is the script that is being referenced in FillHealth. As far as I understand it, since the variable isn't static, then the values should not be shared. It should find fill the health bar for each individual enemy.
public class NormalMonster : MonoBehaviour
{
private float _normalSpeed = 2f;
private float _BaseHealth = 20f;
private float _HealthModifier;
public float setHealth;
public float healthLeft;
// Start is called before the first frame update
void Start()
{
UpdateHealth();
}
// Update is called once per frame
void Update()
{
NormMonMov();
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Arrows")
{
healthLeft -= Arrows.Damage;
Destroy(other.gameObject);
if (healthLeft <= 0f)
{
Destroy(this.gameObject);
EarnedGold.earnedGold += 7;
Spawn_Manager.enemyCount--;
}
}
}
public void UpdateHealth()
{
if (StageMode.StageLvl > 5)
{
_HealthModifier = (StageMode.StageLvl * 0.01f) * _BaseHealth;
setHealth = Mathf.Round(_BaseHealth + _HealthModifier);
}
else
{
setHealth = _BaseHealth;
}
healthLeft = setHealth;
}
public void NormMonMov()
{
transform.Translate(Vector3.left * _normalSpeed * Time.deltaTime);
transform.position = new Vector3(Mathf.Clamp(transform.position.x, -7.0f, 10), transform.position.y, 0);
}
}
Any help would be greatly appreciated for this guy who just start playing with unity this weekend.
I believe the issue is with normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
If you have two Monsters
Both have two scripts attached, FillHealth and NormalMonster
Both the "FillHealth" scripts look for the FIRST gameobject in the scene that has a script with tag NormalMonster so both monsters are pointing to the exact same NormalMonster script (the first in the list)
Change "GameObject" capital G to "gameObject" lower case g
Still not the best way to code this, but that may work I think
Instead of getting the image, get the rect transform, like this
public RectTransform healthBar;
and change the length with:
healthBar.sizeDelta = new Vector2(normalMonster.healthLeft,healthBar.sizeDelta.y);
I have a particle system for when the enemy is destroyed. I have multiple enemies coming from the same path (using the same prefab) and the particle system is only working on the first enemy. Can someone tell me why it is not working on the others as well? Thank you.
EnemyShooting scrip (this is where the piece of code is for the explosion):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyShooting : MonoBehaviour {
[SerializeField] float EnemyLaserSpeed = 10f;
[SerializeField] float EnemyLaserFireTime;
[SerializeField] GameObject LaserBulletEnemyPreFab;
[SerializeField] int MaxNumberOfHits = 1;
public Transform explosion;
int CurrentNumberOfHits = 0;
Coroutine FireCoroutine;
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag == "PlayerLaser")
{
if (CurrentNumberOfHits < MaxNumberOfHits)
{
CurrentNumberOfHits++;
Destroy(collider.gameObject);
Score.ScoreValue += 2;//The user will be rewarded 1 point
}
if (explosion)//EXPLOSION CODE
{
GameObject exploder = ((Transform)Instantiate(explosion, this.transform.position, this.transform.rotation)).gameObject;
Destroy(exploder, 2.0f);
}
}
}
void DestroyEnemy()
{
if(CurrentNumberOfHits >= MaxNumberOfHits)
{
Destroy(gameObject);
EnemySpawner.Instance.OnEnemyDeath(); // Tell the EnemySpawner that someone died
}
}
private void Fire()
{
FireCoroutine = StartCoroutine(ShootContinuously());
}
void BecomeVisible()
{
Fire();
}
IEnumerator ShootContinuously()
{
while (true)
{
GameObject LaserBulletEnemy = Instantiate(LaserBulletEnemyPreFab, this.transform.position, Quaternion.identity) as GameObject;
LaserBulletEnemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0, EnemyLaserSpeed);
EnemyLaserFireTime = Random.Range(0.5f, 0.9f);
yield return new WaitForSeconds(EnemyLaserFireTime);
}
}
// Use this for initialization
void Start () {
BecomeVisible();
}
// Update is called once per frame
void Update () {
DestroyEnemy();
}
}
EnemySpawner : (I thought this script might help in a way so I attached it)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemySpawner : MonoBehaviour
{
[SerializeField] GameObject EnemyPreFab;
[SerializeField] int MaxEnemies = 30;
[SerializeField] float EnemySpawnTime = 1.00001f;
[SerializeField] GameObject FirstWaypoint;
int CurrentNumOfEnemies = 0;
public int EnemiesToNextLevel = 7;
public int KilledEnemies = 0;
public LevelManager myLevelManager;
public static EnemySpawner Instance = null;
int timesEnemyHit;
IEnumerator SpawningEnemies()
{
while (CurrentNumOfEnemies <= MaxEnemies)
{
GameObject Enemy = Instantiate(EnemyPreFab, this.transform.position, Quaternion.identity);
CurrentNumOfEnemies++;
yield return new WaitForSeconds(EnemySpawnTime);
}
}
void Start()
{
if (Instance == null)
Instance = this;
StartCoroutine(SpawningEnemies());
timesEnemyHit = 0;
if (this.gameObject.tag == "EnemyHit")
{
CurrentNumOfEnemies++;
}
}
public void OnEnemyDeath()
{
CurrentNumOfEnemies--;
/*
if (CurrentNumOfEnemies < 5)
{
// You killed everyone, change scene:
LaserLevelManager.LoadLevel("NextLevelMenu");
}
*/
KilledEnemies++;
if (KilledEnemies >= EnemiesToNextLevel)
{
LaserLevelManager.LoadLevel("NextLevelMenu");
}
}
}
Okay so before I begin, Yes I have looked online to find this answer. I've followed advice on multiple other questions, gone through the unity documentation, and done more than a few web searches and nothing I've found so far has solved the error. I'm sure someone will take one look at this and know immediately what's wrong, but as for me, I just can't find it.
Now that that's out of the way Here's the problem. I'm making a Breakout clone and I had everything done, everything working properly. I've got one static class that takes care of the scoring and score related variables, so that other scripts can access them easily. I wanted to practice some basic saving and loading with PlayerPrefs, so I added something for highscores. It's pretty much independent of the other classes, but once I finished that, I started getting a Null Reference Exception in a script that has been done for hours, and was working fine.
I appreciate any help you might have, and any tips for preventing this kind of error the next time around. Sorry It's such a long question.
Here's the full error:
NullReferenceException: Object reference not set to an instance of an object
MenuManager.ActivateLose () (at Assets/Scripts/MenuScripts/MenuManager.cs:31)
Scoring.CheckGameOver () (at Assets/Scripts/Scoring.cs:64)
Scoring.LifeLost () (at Assets/Scripts/Scoring.cs:51)
DeadZone.OnTriggerEnter2D (UnityEngine.Collider2D other) (at Assets/Scripts/DeadZone.cs:22)
And here are the three scripts listed in said error:
using UnityEngine;
using System.Collections;
public class DeadZone : MonoBehaviour
{
public GameObject ballPrefab;
public Transform paddleObj;
GameObject ball;
void Update ()
{
ball = GameObject.FindGameObjectWithTag("Ball");
}
void OnTriggerEnter2D(Collider2D other)
{
//if the object that entered the trigger is the ball
if(other.tag == "Ball")
{
Scoring.LifeLost();
//destroy it, and instantiate a new one above where the paddle currently is
Destroy(ball);
paddleObj.transform.position = new Vector2(0, -2.5f);
(Instantiate(ballPrefab, new Vector2(paddleObj.transform.position.x, paddleObj.transform.position.y + 0.3f), Quaternion.identity) as GameObject).transform.parent = paddleObj;
}
}
}
using UnityEngine;
using System.Collections;
public static class Scoring
{
public static GameObject scoreValue;
public static TextMesh scoreText;
public static int score;
static int multiplier = 0;
static int consecutiveBreaks = 0;
static int lives = 3;
static int totalBricks;
static int remainingBricks;
public static GameObject menuManagerObj;
public static MenuManager menuManager = new MenuManager();
static void Awake()
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
//menuManager = menuManagerObj.GetComponent<MenuManager>();
}
public static void BrickDestroyed()
{
if(scoreValue == null && scoreText == null)
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
}
remainingBricks--;
consecutiveBreaks++;
multiplier = 1 + (consecutiveBreaks % 5);
score += 10 * multiplier;
CheckGameOver();
scoreText.text = score + "";
}
public static void LifeLost()
{
consecutiveBreaks = 0;
multiplier = 1;
score -= 100;
lives--;
LivesDisplay.SetLives(lives);
CheckGameOver();
scoreText.text = score + "";
}
public static void SetBrickCount(int brickCount)
{
totalBricks = brickCount;
remainingBricks = totalBricks;
}
public static void CheckGameOver()
{
//lose condition
if(lives < 0) menuManager.ActivateLose();
//win condition
if(remainingBricks == 0) menuManager.ActivateWin();
}
}
using UnityEngine;
using System.Collections;
public class MenuManager : MonoBehaviour
{
public GameObject winMenu;
public GameObject loseMenu;
public GameObject pauseMenu;
public HighScoreManager highScores;
bool isGamePaused = true;
void Awake()
{
winMenu = GameObject.FindGameObjectWithTag("Win");
loseMenu = GameObject.FindGameObjectWithTag("Lose");
pauseMenu = GameObject.FindGameObjectWithTag("Pause");
}
public void ActivateWin()
{
Time.timeScale = 0f;
winMenu.transform.position = new Vector3(0, 0, -1);
highScores.CompareToHighScores(Scoring.score);
}
public void ActivateLose()
{
Time.timeScale = 0f;
loseMenu.transform.position = new Vector3(0, 0, -1);
}
void ActivatePause()
{
if(isGamePaused)
{
Time.timeScale = 0f;
pauseMenu.transform.position = new Vector3(0, 0, -1);
}
else
{
Time.timeScale = 1f;
pauseMenu.transform.position = new Vector3(35, 0, -1);
}
isGamePaused = !isGamePaused;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Escape))
{
ActivatePause();
}
}
}
The problem is that Scoring is not a MonoBehaviour so the Awake method never gets called. You can try to initialize the fields in a static constructor
static Scoring()
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
//menuManager = menuManagerObj.GetComponent<MenuManager>();
}
or make the Awake method public and call it from another MonoBehaviour.