I’m trying to make a feature for a 2D, procedural generated game that uses Tilemaps.
This feature consists of the player clicking on a tile and deleting that tile from the Tilemap. However, since I have multiple Tilemaps, I can’t just assign one Tilemap to a variable and call GetTile() on that Tilemap, since I need to know exactly which Tilemap the player clicked on.
Physics Raycasts 2D and 3D do not collide with the Tilemap Collider 2D, I’ve tried multiple times and the only way I could get it to work is by adding a Box Collider 2D to the object that has the Tilemap component, which essentially invalidates the shape of the Tilemap and obstructs other Tilemaps that may be under it or by removing the Composite Collider 2D component, which leaves me with bad movement and the player getting stuck everywhere.
Attempting to implement IPointerClickHandler and using OnPointerClick(PointerEventData eventData) does not work either, seeing as the Event System also uses Raycasts.
Attempting to change the Tilemap Collider 2D to not use the Composite Collider on runtime when the player interacts with a tile and turning it back on after the interaction is finished results in a complete slowdown, which is obviously off the table.
I’m completely stumped at this point, why doesn’t the Composite Collider 2D component receive raycasts?
We NEED to know which Tilemap we are interacting with to be able to Set or Replace tiles, but how can we do this if this component doesn’t receive raycasts?
This has been reported multiple times and no proper solution was given, so I’m really hoping to bring some attention to this issue with this thread.
EDIT:
I saw a post where it’s explained that it’s because it’s composed of edges so the inside is empty space, which can’t be hit by a raycast.
But this still seems like a feature that is truly lacking and therefore makes Unity not usable for making games like this.
You are stating that this isn’t having attention without there being an issue/bug. I’ve replied to this and many other things dozens of times so let’s not try to promote this as something that’s being ignored or is something that it isn’t.
The physics system knows nothing about composites, polygon colliders etc. The physics system knows only circles, capsules, edges and polygon primitives so there’s no such thing as raycast doesn’t work on composite. It’s simply misunderstanding.
For instance, if you create an outline of a shape using an EdgeCollider2D then you get physics edge primitives. This is an open shape and there’s no “inside” so a raycast will detect the edge when it crossed an edge only. This is different than all other primitives which are closed i.e. circles, capsules and polygons which do have an inside.
If you use a CompositeCollider2D in Outline mode then you’re asking for edge outlines which are exactly the same primitives as an EdgeCollider2D creates. A raycast will only detect when you cross an edge as expected.
TBH that’s a crazy brute force solution and not required. Also, why does everyone use raycasts for everything? There are so many queries to choose from also ones that will detect contact for you such as IsTouching etc but if you want to detect an “inside” though when using a CompositeCollider2D that doesn’t cross an edge then use Polygon mode for it; it’s as simple as that. You then get polygon primitives. Also, if you want to know if a point overlaps, again using raycast is silly. Use OverlapPoint because this is 2D not 3D. Using a degenerate raycast (zero length or direction) to detect a point shows misunderstanding if that’s what you’re doing.
You are also NOT forced to use a CompositeCollider2D. Why are you adding it? Another thing is why are you using physics collider if you are simply trying to determine which tile position. There are all sorts of world point → tile position conversion methods on the tilemap itself which are nothing to do with physics.
Finally, not sure why you’ve added a BUG tag; nothing here is a bug.
I apologise for “trying to promote this as something that’s being ignored or is something that it isn’t.”.
It was quite frustrated when writing this post because absolutely nothing I tried worked and I couldn’t find the solutions you listed as alternatives online when I looked this “issue” up.
I needed a query not related to where or what the player was touching, but related to the mouse’s position so the player could click on blocks that were far away as well, so I don’t think IsTouching would work. Then again, I didn’t think to try it so I’m not sure if it would work or not. Just assuming from reading the documentation.
Without a CompositeCollider2D component, my player gets “stuck” on the tiles since each tile is one collider instead of it being an entire collider like what the component makes it into.
That’s the only reason I use it and whenever I look up that issue, that’s the solution people give.
I don’t know another way to do it.
The player does not get stuck with it enabled, so I assumed the CompositeCollider2D was the component solving that issue.
Thank you for giving me a solution for my problem, I will attempt it.
I also did not know that option worked that way, I should have checked the documentation more thoroughly.
I’d like to remove the tag but I’m not sure if that is possible, I apologise.
I’d like to thank you for taking your time to reply to my post, I know it must be difficult with the amount of posts that this forum gets per day and once again, apologies for overreacting. This has been a learning experience for me.
It’s okay and I’d still like to help you find a solution that works for you. The main problem I encounter a lot and I’m not saying that this is the case here, is that features are used way outside of their purpose.
So if you have a mouse position you can convert that to a world position and then use the Tilemap.WorldToCell(worldMousePos) to convert to a logical cell position. You can then use Tilemap.HasTile(tilePos) to determine if there’s a tile there or empty space.
All I need to do is call Tilemap.WorldToCell(worldMousePos)
to get the cell and then determine if it’s empty or not by using Tilemap.HasTile(tilePos)?
My issue here is that in order to call those functions, I need a Tilemap. And I have multiple ones, so I’d like to be able to detect which tilemap the player clicked on. Can I use the Polygon mode of the Composite Collider 2D to get the Tilemap with a raycast? Or should I use OverlapPoint like you suggested?
That’s my only issue right now, getting the Tilemap, which is why I was trying to use a raycast before not knowing the Composite was edges and not a full collider.
EDIT:
I am currently doing this (I apologise in advance for my rough code):
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButton(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity, InteractableLayerMask);
Debug.DrawLine(ray.origin, hit.point, Color.blue);
if (hit.collider)
{
Debug.Log(hit.collider.gameObject.name);
ActiveTilemap = hit.collider.gameObject.GetComponent<Tilemap>();
}
}
if (ActiveTilemap)
{
Vector3Int gridPosition = ActiveTilemap.WorldToCell(mousePosition);
TileBase clickedTile = ActiveTilemap.GetTile(gridPosition);
if (clickedTile)
{
if (Vector2.Distance(transform.position, new Vector2(gridPosition.x, gridPosition.y)) <= MaxMiningDistance)
{
if (FindObjectOfType<TilePrefabManager>().TilePrefabDictionary.ContainsKey(clickedTile))
{
GameObject toInstantiate = FindObjectOfType<TilePrefabManager>().TilePrefabDictionary[clickedTile];
if (toInstantiate)
{
Instantiate(toInstantiate, gridPosition, Quaternion.identity);
}
}
ActiveTilemap.SetTile(gridPosition, null);
}
else
{
Debug.Log("Block is too far away to mine!");
}
}
ActiveTilemap = null;
}
Getting the Tilemap when the Composite Collider 2D mode is set to Polygon works! However, I’m getting very bad slowdowns when attempting to break blocks. It slows down from 2000 to around 10 FPS.
I removed the instantiating code but it still slows down, so that’s not the problem.
Is there any way to optimise this?
Sorry to double post but the content filter isn’t letting me edit my reply due to “inappropriate elements”.
Changing the mesh generation type to Manual instead of Synchronous and calling GenerateGeometry() through code basically results in the same slowdown as it did when using just Synchronous.
Changing the mode to Polygons to be able to get the Tilemap is probably way too heavy on the performance ^^’
OverlapPoint doesn’t work either when the mode is not set to Polygons since there’s no collider, only edges, so I can’t test the performance.
The player gets stuck on individual blocks without Composite so it can’t be removed.
I took a screenshot which can be seen here (couldn’t upload it as an image and the image insert didn’t work):
Trying to move to the right makes it so the player can’t move since it’s “stuck” on the edge of the block.
We seem to be going around a little here. To clarify:
You cannot use physics queries to detect the inside of edges (EdgeCollider2D or CompositeCollider2D in outline mode) as there is no inside
If you absolutely MUST use a CompositeCollider2D because you want continuous edges then you have to live with not being able to use physics to detect the logical inside.
Using CapsuleCollider2D for things moving across polygons can often help beause the collision normals are not aligned with the movement. It won’t stop it, just reduce impulses along the motion direction
Switching between Outline and Polygon mode in the CompositeCollider2D means it has to recalculate all of the Collider2D that are using it so that can get very expensive, especially for TilemapCollider2D where doing it in realtime isn’t feasible
Basically what you’ve said is you must use CompositeCollider2D in Outline but need to use physics queries to detect an inside but as I’ve said, edges don’t have an inside, they’re just lines and can form outlines only.
I am aware of those things, as you said.
I’m just not sure how I can implement the system I want while using the Edges mode since the performance in Polygon mode is clearly not good enough to update the mesh during runtime every time the player breaks a block.
The only thing I can think of is making each Tilemap manage click on it through a script, that way I wouldn’t need to know which Tilemap it is.
I’ll probably try that and change the collider to a capsule one.
Thanks again for your help, Melv!
If anyone is still looking to an easy solution, try setting the “Geometry Type” parameter on the Composite Collider 2D to “Polygons”. It worked for me!
Rather than going with the Raycasts, why don’t you use IsTouchingLayers method from Rigidbody2D for the above purpose:
bool IsGrounded()
{
// Solution when an object has Rigidbody attached
// Check if the player is touching the ground
return rb.IsTouchingLayers(LayerMask.GetMask("Ground"));
}
When using Tilemap Collider 2D along with Composite Collider 2D, Rigidbody2D will be available in the required components anyway. So going with: Rigidbody2D.IsTouchingLayers() will be your ticket to go.