Destroying object at raycast 2D causes unrelated object to be destroyed - c#

In my game the user can spawn instantiated versions of 2 prefabs with mouse clicks: tiles and balls. The balls can be spawned over the tiles.
The user can also erase objects which have been spawned. This is achieved by placing the following code in a if(Input.GetMouseButtonDown(0))) block of the update method:
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Input.mousePosition;
mousePos.z = canvas.transform.position.z;
Vector3 mousePosTrans = Camera.main.ScreenToWorldPoint(mousePos);
RaycastHit2D rayHit = Physics2D.Raycast(mousePosTrans, Vector2.zero);
if (eraserSelected)
{
Destroy(rayHit.collider.gameObject);
}
}
The objects intended for deletion are being deleted, but the problem is that other unrelated and distant objects are sometimes also simultaneously deleted. For example, if I spawn 2 squares and a ball on each square, then delete the first ball, when I go to delete the first square the second ball is simultaneously deleted. Sometimes when an object is deleted another object is simultaneously "un-deleted"!
Objects are spawned with the following code:
TileOrBall obj = (TileOrBall) Instantiate(tileOrBall, pos, Quaternion.identity);
obj.transform.SetParent(canvas.transform);
where TileOrBall is the class of the objects being spawned and tileOrBall is a reference to one of the two prefabs from this class.
So is there something wrong with the approach I'm taking to object spawning or destruction? How can I solve the simultaneous deletion (and sometimes resurrection) of distinct objects when trying to destroy an instance of these objects?
EDIT:
Adding link to zip file of project:
https://drive.google.com/open?id=0B28_MgFRWv97OWxfNE96LXcxZHc
To replicate the issue, click on the square in the side bar and "paint" squares in the grid wherever, then click on the ball in the side bar and "paint" over the squares that have been placed. Finally, click on the eraser icon and start deleting objects by clicking on them. You will see that objects you did not click on are deleted, and you may even see that objects are resurrected when you try deleting objects.
Thanks!

Found few problems in the Project.
1.When you do Destroy(unitAtMouse);, you are not deleting the GameObject from the raycast. You are ONLY deleting the Unit script that is attached to that GameObject.
Because this deletion failed, you will have multiple multiple Square GameObjects in one spot. That makes it harder to delete later on since you have to click delete multiple times until the last Square GameObjects is deleted.
To fix this, replace Destroy(unitAtMouse); with Destroy(unitAtMouse.gameObject);.
2.It is good to have the GameObject you are instantiating to be GameObject instead of script. For example public Unit selectedObject; should be public GameObject selectedObject;
3.Set eraserSelected to false when the square or circle in the side bar is clicked. This fixes problems that even user selects the square while again while in eraserSelected mode, they can delete or even create new square. You want to disable eraserSelected if square or circle in the side bar is clicked. In another word, if setSelectedObject is called.
4.The Erase Button should be under the panel like the other buttons too. Otherwise, it will go missing under some screen resolution.
5.Fixed enum to use the actual enum instead of enum.string.
6.Fix a problem that made the ball invisible which makes it look like it was deleted. What happens is that the ball is sent behind the square. Fixed this by making the ball sortingOrder to the 1 and the square sortingOrder to be 0.

Alternatively, building on #Programmer's response you could create a new script, say DeletableObject, that can be completely empty for the time being and attach this to the ball and square prefabs and any future prefabs you may have. Then in the suggested code above, change:
if (rayHit.collider.CompareTag("ball"))
to
if(rayHit.collider.GetComponent<DeletableObject>())
That way you can attach the script to any future prefabs you make to be able to delete them.
On a side note, if you're adding / deleting lots of objects and performance becomes an issue, look into Object Pooling. In fact, you should look into it anyway as it's a good habit to get into.

Related

OnTriggerEnter2D working only when the game starts, i want it to check for collisions every frame

I am working on a game in unity, a type of sliding number puzzle where you have to line up each piece in ascending order. My code of checking collisions is only working when I start the game, so if I move a piece, it won't tell the near pieces that I moved the near piece. I hope you understood my problem, every help is appreciated. Thank you.
This is how my code looks with the up check. I
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.name == "Piece" || collision.gameObject.name == "Up_Collider")
{
piece_Script.Collide_Up = true;
Debug.LogError("Up");
}
else
{
piece_Script.Collide_Up = false;
}
}
I Thought that the else statement will solve this problem but it didn't.
Make sure all the things I write below are correct in your project:
1- You are using "2D" colliders on both objects.
2- At least 1 of the objects has a "Rigidbody2D".
3- IsTrigger Checkbox is active in your collider.
4- You are using the correct Tag.
5- Check layer collision matrix in - Edit > Project Setting > Physics2D.
And it is better to use tag instead of name
Based on your code in your question and your video, I see at least a couple issues.
First, there's an issue with how you're moving pieces. Your movement code for a Piece is:
Piece.transform.position = Vector3.Lerp(a, b, t);
This is not correct if you want to trigger collider events. These require interactions with rigidbodies, which means you should instead be changing the position of the piece's rigidbody with Rigidbody2D.MovePosition(). This will move your rigidbody within the physics system, which means taking into account collisions/triggers.
So (assuming you get the rigidbody for each Piece when you initialize them), your code might look like:
// Note also that I used Vector2 here - it's best you keep it consistent
pieceRigidbody.MovePosition(Vector2.Lerp(a, b, t));
Second, OnTriggerEnter2D() is not fired when two colliders stop touching. So I don't see your "else" condition being particularly useful. To achieve that with your current code, you could theoretically introduce OnTriggerExit2D() to take care of the "else" condition. However, you run into complications because while moving, a Piece may Enter and Exit the same directional colliders of two other Pieces before coming to rest. You'd have to take into account the order that occurs to get an accurate final state.
While workable, I have an alternative approach to suggest: Abandon using Trigger Collider events, and only check for a valid move at the time a Piece is clicked. Eliminate the collider event handlers, and just execute a Physics2D.OverlapBox() in each position around a Piece when it is clicked. If the position is occupied by another piece or an edge, that's not a valid move. If there is a position without something blocking it, then move the piece there.

How can I affect the transparency of many sprites at once through the parent GameObject?

I have a player that can move around a randomly generated map of rooms. I've been trying to figure out a way to keep all the rooms fully transparent/invisible, and then as soon as the player enters the room, that room becomes opaque.
I already have a way to detect the room the player is in, the main issue I'm having is changing the transparency of the room. Each room is made up of 30 or so "wall" tiles, which are just default square 2D sprites for now. These are the only thing I need to change the visibility of for now.
I saw that I can change the "material render value" or something similar to that per sprite, but I'm not sure of an easy way to do that for a whole lot of sprites at once that only have the default sprite renderer component and not a custom material.
Do I need to completely overhaul my rooms' walls to be one prefab for the whole wall? Or is there some way I could easily loop through each wall in a targeted room and change the transparency/visibility?
You could use the GameObject.SetActive(bool) it has the utility to disable and enable the visibility of the GameObject.
(I believe you have already done this, but if not) In your hierarchy you would first create an Empty and add your room to it, rename it with the name of your room and you could use the following script when detecting the room the player is in.
public GameObject room; //link your room here
void Start()
{
room.SetActive(false);
}
void Update()
{
//When detecting the room
room.SetActive(true);
}
The bool parameter of the SetActive() void sets whether the object is visible or not, when false it is invisible, and when true it is visible. I don't have your script as a reference, so I used an example
You should use GetChild() and for {}. Get child gets the child of the index you chose, and accesses it. Here is the script:
void Update()
{
for (int i = 0, i <= transform.childCount, i += 1)
{
GameObject child = GetChild(i);
// change the values here using child.GetComponent<>()
}
}
This is what the computer does: sets int i. Does this object have less children than the variable, i? Yes, run the code underneath. set child to the child of index i. i increases by 1. Repeat until i is greater than the child count.
Links:
https://docs.unity3d.com/ScriptReference/Transform.GetChild.html
https://www.w3schools.com/cs/cs_for_loop.asp

Instantiate prefabs on endless runner top of previous one

I am developing 2D Endless racer game, insted of constant background; i will use prefabs as background, i have 3 different prefabs as background: (Please Check Image)
Straight Road
Right Curved Road
Left Curved Road
I want to instantiate endless prefabs, according to instantiate point of each prefab,
I would appreciate for your support
Goal Image and Prefabs
One simple solution come straight from the example image you posted. Each road piece has its own "exit point", symbolized by that yellow circle. Since each road piece is its own prefab, you could give each of them an empty child object, acting like your yellow circle.
Each object would then have a reference to that object, and would use that to spawn the next object.
public class RoadPiece : Monobehaviour
{
public Transform nextInstantiatePoint;
To make the next road piece line up correctly, you could take advantage of the Sprite's Import Settings. Set the Pivot to "Bottom", or try the others for different effects.
The algorithm that you use to chose which piece to instantiate each time is up to you, but to start you could have an array of all the road pieces and pick one from there.
Ideally you want each object to wait some time before it instantiates the next, otherwise it will quickly cause the scene to be overcrowded. The easiest way to do it is like this:
void Start()
{
Invoke(nameof(SpawnNext), waitTime);
}
void SpawnNext()
{
Instantiate(roadPiece, nextInstantiatePoint.position, nextInstantiatePoint.rotation);
}
}

Instantiating a U.I object at the position of another object while keeping the Canvas as the parent

Ran into a U.I problem that I am struggling to solve (UI is always an issue for me). Gist of the project is that I have an array of spawn points (Positions[i]), an array composed of enemy prefabs(Enemies[j]) and just a UI panel prefab with a text component (EnemyHUD). When I pass string names through a certain function, as long as there is a prefab with the same name and position available to it, it will load in the enemy. Now, for every enemy prefab loaded in, I would like a "EnemyHUD" prefab to instantiate at at the enemy positions and with text displaying the name of the enemy.
GameObject HUD = Instantiate(EnemyHUB, Positions[i].position, Positions[i].rotation);
This line spawns the EnemyHUD prefab at the right location but its Instantiates them outside of the canvas so they show up as red x's. So I added this:
HUD.transform.SetParent(GameObject.Find("Canvas").transform, false);
This fixes the issue of Instantiating outside of the canvas but also (irritatingly)resets the Instantiate position and embarrassingly, I'm not sure how to set it back while making sure it stays a child of the Canvas. Have not event touched the name change part yet.
I have been working on this this since last night and while I got a lot of different results experimenting, none have been the one that I want. Very much still a novice, so I am sure it is looking me in the face so please help me find it.
First of all I would strongly recommend to avoid Find whenever possible! Rather already reference the according Canvas and store its reference. Especially the i lets assume you are doing this in a loop so using Find repeatedly is extremely inefficient!
// Drag you Canvas object into this field via the Inspector in Unity
[SerializeField] private Canvas _parentCanvas;
private void Awake()
{
// use this only as fallback an only ONCE
if(!_parentCanvas) _parentCanvas = GameObject.Find("Canvas");
if(!_parentCanvas) Debug.LogError("Could not get Canvas!", this);
}
Well and then the second parameter of Transform.SetParent is called
worldPositionStaysIf true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.
And
The default value of worldPositionStays argument is true.
Simply either leave it out like
HUD.transform.SetParent(_parentCanvas);
// this equals
//HUD.transform.SetParent(_parentCanvas, true);
or you could even do it in one single go already in Object.Instantiate which takes an additional parameter
parent Parent that will be assigned to the new object.
// This directly instantiates the new hud as child of _parentCanvas
// while still taking the position and rotation in absolute worldspace
GameObject HUD = Instantiate(EnemyHUB, Positions[i].position, Positions[i].rotation, _parentCanvas);

Unity Rigidbody has velocity but not moving

I have a game in Unity where cubes are repeatedly moved past the camera. I move the cube using a script where I set its Rigidbody's velocity each update. Once it moves out of view my script instantiates a new cube on the other side which begins the process again.
Recently I've found that it works fine for a random amount of cubes before, seemingly randomly, a cube is instantiated that does not move. Using the inspector I can see that this object has velocity. If I move it even a small amount using the editor it starts to move as normal.
Has anyone seen something like this before?
I'm fairly certain the problem was related to the fact I was trying to directly modify the velocity( The physics engine decided the object was at rest and stopped it moving. ). By setting the object to be kinematic and modifying its position in my code I solved the problem.
May be you are changing the velocity of your gameObject when it goes through a specific coordinate (in an if statement for example), unity is not very accurate sometimes with coordinates so it may be happening that the condition is never met. Change that condition and add a margin range to solve this error.

Categories

Resources