Ball appears to pass through brick intermittently

Started by
17 comments, last by Scouting Ninja 5 years, 10 months ago

I've got a bug with my brick breaker style game. The bricks move down one line at a time ever 1.5 seconds. What appears to be happening is occasionally the ball will be just about to hit the brick when the brick moves down a line, and now the ball is behind it. I'm not sure how to fix this. I have two ideas but I'm not sure of implementation. 1 solution would be to check where they were and where they are going to be before rendering the frame. Then if they crossed paths, then register the brick as hit. Solution 2 would be change how the bricks move. I could maybe slide them down line by line, instead of a jump down. I'm not sure of this will fix the issue or not. Any ideas?

Advertisement

You can try the Unity LateUpdate here. You could also use OnCollisionStay2D instead of OnCollisionEnter2D.

16 hours ago, Scouting Ninja said:

You can try the Unity LateUpdate here. You could also use OnCollisionStay2D instead of OnCollisionEnter2D.

So for LateUpdate, I would do the bricks, right? So that way we wold first check if they hit, and if they did destroy any appropriate bricks, and then the remainder would move?

Also, a friend had recommending using Lerp to just kind of slide them down so that they are not moving one whole line all at once. Moving them 1 unit over time would allow the ball to hit correctly, right? Would this be a viable solution?

2 minutes ago, ethancodes said:

Also, a friend had recommending using Lerp to just kind of slide them down so that they are not moving one whole line all at once. Moving them 1 unit over time would allow the ball to hit correctly, right? Would this be a viable solution?

Yes very acceptable. Give this a try.

 

4 minutes ago, ethancodes said:

So for LateUpdate, I would do the bricks, right?

Yes. The way this works is it updates after everything else, so before the new cycle of physics updates start. There is more physics updates per second than frames rendered, late update often fixes collision problems in unity between none dynamic and dynamic objects.

This appears to be working now. I do have a random error that has been happening (even before I fixed this). Occasionally I get an error AFTER ending the game, that says "Can't destroy Transform component of 'InsertAnyBrickTypeHere'. If you want to destroy the game object, please call 'Destroy' on the game object instead. Destroying the transform component is not allowed." This error shows up with no apparent pattern that I could find. It'll show up with or without explosive bricks present, whether I destroyed 1 brick or more than one, whether I hit 1 or more explosive brick, whether I end the game by losing or by hitting the stop button. It may not show up for 5-10 test plays, and then all the sudden it pops up. I have never seen it if we do not destroy any bricks. I can't figure out why it is giving this error. I checked through my Brick and ExplosiveBrick code and can't find anywhere that I'm trying to destroy a Transform. Any ideas?

27 minutes ago, ethancodes said:

can't find anywhere that I'm trying to destroy a Transform. Any ideas?

This happens some times when you link a scene object using a public transform instead of public GameObject.

A other thing could be if you are checking for child objects, Unity often returns transforms when you ask for a child. You fix this by adding .gameObject after it.

The Unity find by name function can also return a transform.

 

With out viewing your code it is hard to tel where you are using a transform instead of a child.

Alright this gives me an idea. I had changed the way the bricks move by putting them inside of a parent object and then moving that object. Because of this there are several places that are set up with some of the possible causes you mentioned. I'll check them all out and see if I can fix it.

Alright I've been hunting for this error and can't figure it out to save my life. I've tried commenting out sections of code that could possibly have to do with it, but haven't had any luck stopping the error. I'll post the code and maybe you can spot what i'm missing. 


public class Brick : MonoBehaviour {

	public AudioClip crack;
	public Sprite[] hitSprites;
	public static int breakableCount = 0;
	public GameObject smoke;

	protected LevelManager levelManager;
	public int maxHits;
	public int timesHit;
	private bool isBreakable;

	// Use this for initialization
	void Start () {
		isBreakable = (this.tag == "Breakable");

		//keep track of breakable bricks
		if (isBreakable){
			breakableCount++;
		}
		timesHit = 0;
		levelManager = GameObject.FindObjectOfType<LevelManager>();
	}

	void OnCollisionEnter2D (Collision2D collision)
	{	//if brick is at end of screen, game over
		if (collision.gameObject.tag == "EndScreen") {
			levelManager = GameObject.FindObjectOfType<LevelManager>();

			levelManager.LoadLevel("Lose");
		}
		//play audio clip when ball hits brick
		AudioSource.PlayClipAtPoint (crack,transform.position);
		if (isBreakable) {
			HandleHits ();
		}
	}
	//track number of times brick is hit, destroy brick when maxHits is reached
	void HandleHits ()
	{
		timesHit++;
		maxHits = hitSprites.Length + 1;

		DestroyBrick();	
	}

	public virtual void DestroyBrick ()
	{
		if (timesHit >= maxHits) {
			breakableCount--;
			//levelManager.BrickDestroyed ();
			PuffSmoke ();
			Destroy (this.gameObject);
		} else {
			LoadSprites ();
		}
	}

	protected void LoadSprites ()
	{
		int spriteIndex = timesHit - 1;
		if (hitSprites [spriteIndex] != null) { //if statement keeps it from loading nothing if level designer forgets to assign a sprite
			this.GetComponent<SpriteRenderer> ().sprite = hitSprites [spriteIndex];
		} else {
			Debug.LogError("Brick sprite missing");
		}
	}

	protected void PuffSmoke ()
	{
		var smokePuff = Instantiate(smoke, transform.position, Quaternion.identity);
			var effect = smokePuff.GetComponent<ParticleSystem>().main;

			effect.startColor = GetComponent<SpriteRenderer>().color; 
	}
}

public class BoardManager : MonoBehaviour {


		private int columns = 20;                //Number of columns in our game board.
		private int rows = 22;  				//Number of rows in our game board. Only 16 - 22 are available for brick spawning

		public int brickCount;               //Number of bricks to spawn.
		public GameObject brickHolder;
		public GameObject target;			//location of object that bricks are moving towards
		public float speed;

		protected float delay = 5.0f;                //Delay till bricks move down 1 row.

		//Define brick types to assign sprites to in editor
		public GameObject oneHitBrick;                                
		public GameObject twoHitBrick;                                  
		public GameObject threeHitBrick;                                 
		public GameObject explodingBarrel;  //temporarily represented by the unbreakable brick

		public List<GameObject> bricks = new List<GameObject>();

		private GridSquare randomPosition;		//Position at the randomIndex
		private GridSquare[,] gridPositions = new GridSquare[20,23];   //list of positions on game board
		private List<GridSquare> gridOptions = new List<GridSquare>(); //list of options to place next brick
		private GridSquare tempPosition;


		// Use this for initialization
		void Awake ()
		{
			brickHolder = Instantiate(brickHolder, Vector3.zero,Quaternion.identity) as GameObject;
		}
		void Update ()
		{
			transform.position = Vector3.MoveTowards(transform.position, target.transform.position , speed * Time.deltaTime);
		}

		//Clears our array gridPositions and prepares it to generate a new board.
		void InitialiseList ()
		{
			//Clear our array gridPositions.
			Array.Clear(gridPositions, 0, gridPositions.Length);

			//Loop through x axis (columns).
			for(int x = 0; x < columns; x++)
			{
				//Within each column, loop through y axis (rows).
				//start at 16 to spawn our bricks offscreen
				for(int y = 0; y <= rows; y++)
				{
					bool visible = true;
					//At each index add a new Vector3 to our array with the x and y coordinates of that position.
					gridPositions[x,y] = new GridSquare(new Vector3(x,y,0), visible);
				}
			}
		}
	
		//RandomPosition returns a random position from our gridPositions array.
		GridSquare RandomPosition ()
		{
			if (AllSquaresVisible()) //check if all gridPositions are unoccupied
			{
				//set randomPosition to a random element in gridPositions array
				randomPosition = gridPositions[Random.Range(0,gridPositions.GetLength(0)),Random.Range(16,gridPositions.GetLength(1))];

				//ensures the GridSquare will not be re-used 
				randomPosition.IsVisible = false;

				//Return the randomly selected GridSquare
				return randomPosition;
			} 
				//call CheckBorders to populate list gridOptions
				CheckBorders(randomPosition); 

				//set randomPosition value to a random number between 0 and the count of items in our List gridOptions.
				randomPosition = gridOptions[Random.Range(0, gridOptions.Count)];

				//make sure the chosen GridSquare can't be re-used
				randomPosition.IsVisible = false;

				//Return the randomly selected Vector3 position.
				return randomPosition;	
			

		}
		//Check to see if all squares are visible
		private bool AllSquaresVisible ()
		{
			for (int i = 0; i < gridPositions.GetLength (0); i++)
			{
				for (int j = 16; j < gridPositions.GetLength (1); j++) 
				{
					if (!gridPositions [i, j].IsVisible) 
					{
						return false;
					}
				}
			}
			return true;
		}
		//check borders is used to check above, below, left, and right of the current GridSquare to see if those options are available for next brick, and then each
		//available option is added to list gridOptions
		public void CheckBorders (GridSquare currentPosition)
		{
			gridOptions.Clear (); //empty gridOptions list

			if (currentPosition.GridPosition.x != 0) { //check to make sure we are not on column 1, if not, check 1 column to the left. If available, set to gridOptions list
				tempPosition = gridPositions[(int)currentPosition.GridPosition.x - 1, (int)currentPosition.GridPosition.y];

				if (tempPosition.IsVisible == true) {
					gridOptions.Add (tempPosition);
				}
			}

			if (currentPosition.GridPosition.x != 19) { //check to make sure we are not on column 19, if not, check 1 column to the right. If available, set to gridOptions list
				tempPosition = gridPositions[(int)currentPosition.GridPosition.x + 1, (int)currentPosition.GridPosition.y];

				if (tempPosition.IsVisible == true) {
					gridOptions.Add (tempPosition);
				}
			}

			if (currentPosition.GridPosition.y != 16) { //check to make sure we are not on row 18, if not, check 1 row under. If available, set to gridOptions list
				tempPosition = gridPositions[(int)currentPosition.GridPosition.x, (int)currentPosition.GridPosition.y - 1];

				if (tempPosition.IsVisible == true) {
					gridOptions.Add (tempPosition);
				}
			}

			if (currentPosition.GridPosition.y != 22) { //check to make sure we are not on row 30, if not, check 1 row up. If available, set to gridOptions list
				tempPosition = gridPositions[(int)currentPosition.GridPosition.x, (int)currentPosition.GridPosition.y + 1];

				if (tempPosition.IsVisible == true) {
					gridOptions.Add (tempPosition);
				}
			}
		}

		//LayoutObjectAtRandom accepts an array of game objects to choose from along with a minimum and maximum range for the number of objects to create.
		void LayoutObjectAtRandom (int brickCount)
		{
			bricks.Add(oneHitBrick);
			bricks.Add(twoHitBrick);
			bricks.Add(threeHitBrick);
			bricks.Add(explodingBarrel);

			//Instantiate objects until the randomly chosen limit objectCount is reached
			for(int i = 0; i < brickCount; i++)
			{
				//Choose a position for randomPosition by getting a random position from our list of available Vector3s stored in gridPosition
				GridSquare selectedPosition = RandomPosition();

				GameObject brick = bricks[Random.Range(0,bricks.Count)];

				//Instantiate tileChoice at the position returned by RandomPosition with no change in rotation
				selectedPosition.AssociatedBrick = Instantiate(brick, selectedPosition.GridPosition, Quaternion.identity, brickHolder.transform);

			}
		}


		//SetupScene initializes our level and calls the previous functions to lay out the game board
		public void SetupScene (int level)
		{
			//Reset our list of gridpositions.
			InitialiseList ();

			brickCount = (int)Mathf.Log(level, 2f); //set the number of bricks based on the current level

			//Instatiate random bricks based on level
			LayoutObjectAtRandom (brickCount);
		}
}

These are the only two scripts that I can think would be causing this error. I have 2 other scripts that I've recently worked with, but neither of them have their game objects instantiated when I'm getting these errors, so I don't think that has anything to do with it.

On 5/22/2018 at 5:08 AM, ethancodes said:

It may not show up for 5-10 test plays, and then all the sudden it pops up.

This tells me it could be a brick; like one of your existing bricks has a typo somewhere.

52 minutes ago, ethancodes said:

I'll post the code and maybe you can spot what i'm missing. 

That code looks fine to me.

The only part that could maybe be responsible is this:


		//Clears our array gridPositions and prepares it to generate a new board.
		void InitialiseList ()
		{
			//Clear our array gridPositions.
			Array.Clear(gridPositions, 0, gridPositions.Length);

			//Loop through x axis (columns).
			for(int x = 0; x < columns; x++)
			{
				//Within each column, loop through y axis (rows).
				//start at 16 to spawn our bricks offscreen
				for(int y = 0; y <= rows; y++)
				{
					bool visible = true;
					//At each index add a new Vector3 to our array with the x and y coordinates of that position.
					gridPositions[x,y] = new GridSquare(new Vector3(x,y,0), visible);
				}
			}
		}

You call array clear, but it is unknown if you destroy the old objects in the array. Sometimes if an array holds gameObjects you must first destroy all the game objects before clearing the list.

If you don't there could be some left over objects in the scene.

This topic is closed to new replies.

Advertisement