- Home /
How to place blocks so they stick to a surface in any orientation? And stick to a grid?
I'm making a game where one of the mechanics involves placing prefab building blocks, and I want them to stick to a grid and stick to other already placed blocks to make building easier. I've tried different approaches and I'm starting to get confused.
My main issue comes with placing the blocks at the right distance from a surface; when you place them on perfectly flat ground it's easy enough to figure it out, but I want to allow the player to place blocks on walls without changing the block's pitch/roll. This means the block will always be upright, and will only rotate on the Y-axis.
Right now here's what I've implemented: when you point towards a surface you can build on, a "ghost block" appears that shows you how the block will look once placed, and also checks whether or not the block is intersecting anything. Left-click spawns the block on this position, right-click applies a 45-degree rotation to the block, and the scroll wheel allows you to cycle through different blocks, which are chosen from an array in my code.
This is the method where, after a raycast to check if the player is pointing towards a surface you can build on that is close enough, the position and rotation of the block are calculated, and it also checks if the block can be placed at that position. This code works properly when placing on the floor. When placing blocks on walls, however, since I'm only using the vertical scale of the block to calculate the distance it should spawn from the surface, it doesn't work.
bool CheckPlaceable(RaycastHit hitInfo)
{
Collider other = hitInfo.collider;
ghosts[selectedBlock].SetActive(true); //activate the ghost
normal = hitInfo.normal; //normal of the face the raycast has hit
eulerRotation = new Vector3(0, angle); //rotation that the player has assigned to the block
position = (hitInfo.point + (blocks[selectedBlock].transform.localScale.y / 2 * normal)); //places the block on top of the point
ghosts[selectedBlock].transform.position = position;
ghosts[selectedBlock].transform.localRotation = Quaternion.Euler(eulerRotation); //rotate the block
if (!ghostCollision.Intersecting) //if the block isn't going to be placed where it intersects with
//anything (the ghost block serves to check this)
{
ghostRenderer.material = ghostPlaceable;
return true;
}
else
{
ghostRenderer.material = ghostNotPlaceable;
return false;
}
}
And my second question, how can I implement a grid system in a way that doesn't interfere with this? I already tried simply rounding the block's position to the unit but it was a bit janky since it didn't take sticking to surfaces into account.
Answer by AlrexXboy · Jun 16, 2021 at 02:57 PM
I think I figured out a solution for the first question, by using the bounding box of the block and the normal and point of the raycast. This works for surfaces lined up with world coordinates, but I can't get any further with this approach I think. Still, close enough.
Renderer blockMesh = currentBlock.GetComponent<Renderer>(); //Get the renderer's bounding box
Vector3 blockBounds = blockMesh.bounds.size; //take the rotation into account from the start by using bounding box
Debug.Log(blockBounds);
Vector3 moveVector = new Vector3(normal.x * blockBounds.x, normal.y * blockBounds.y, normal.z * blockBounds.z) / 2;
Debug.Log("move by " + moveVector);
position = hitInfo.point + moveVector;