- Home /
Make an instantiated object match the slope of the terrain
I'm trying to get my instantiated building to not clip through the terrain when I build on a slight slope. I've managed to get the building to drop to the terrain y coordinate but the x and z rotations of the building are not consistent with the gradient of the slope and so the building cuts through the terrain.
I've been researching this for about two hours and I'm finding it really hard to understand the posts about raycast normal. Can someone please explain how to do this in very simple terms? Here is the script:
void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if(currentlySelectedBuilding != null)
{
if(Physics.Raycast(ray, out hitInfo, Mathf.Infinity))
{
if(hitInfo.collider.tag != null && hitInfo.collider.tag == "Ground")
{
Vector3 groundPosition = hitInfo.point;
//I'm rounding the coordinates so it's easier to place buildings in perfectly straight lines etc.
int x = Mathf.FloorToInt(hitInfo.point.x);
int z = Mathf.FloorToInt(hitInfo.point.z);
roundedMousePosition.x = x;
roundedMousePosition.z = z;
//this allows me to place buildings on the same level as the slope
roundedMousePosition.y = groundPosition.y;
//I need some code here to change the rotation of currentlySelectedBuidling so matches the slope:
//??????????????????????????????????????????//
//make the gameObject equal to the mouse position so I can move it around the map
currentlySelectedBuilding.transform.position = roundedMousePosition;
//this script detects when the building is hovering over another object that should be built on.
oCD = currentlySelectedBuilding.GetComponent<ObjectCollisionDetecter>();
//rotate building while it's hovering
if(Input.GetKeyUp("r"))
{
currentlySelectedBuilding.transform.Rotate(0,-90,0);
}
if(Input.GetMouseButtonDown(0) && oCD.objectBelow == false )
{
//sets the building at the location it was at when mouse0 was hit.
currentlySelectedBuilding.transform.position = roundedMousePosition;
//I had to make the prefab ignore raycast hit while it was moving otherwise it interacts with the raycast and creates weird artefacts.
//since I now want the ray to detect it, I changed it back to obstacle.
currentlySelectedBuilding.layer = LayerMask.NameToLayer("Obstacle");
//this gets around the building(clone) problem.
currentlySelectedBuilding.name = buildingName;
//update graph so units avoid building
AstarPath.active.UpdateGraphs(currentlySelectedBuilding.collider.bounds);
//make the building and ocd script null.
currentlySelectedBuilding = null;
oCD = null;
}
else if (Input.GetMouseButtonDown(0) && oCD.objectBelow == true)
{
Debug.Log("You must build somewhere else!");
}
}
}
}
}
Answer by robertbu · Nov 15, 2014 at 03:24 AM
You can do something like this:
function SpawnObject(prefab : GameObject, worldPos : Vector3) {
var hit : RaycastHit;
if (Physics.Raycast(worldPos, Vector3.down, hit)) {
var go = Instantiate(prefab, hit.point, Quaternion.identity);
go.transform.rotation = Quaternion.FromToRotation(transform.up, hit.normal) * go.transform.rotation;
}
}
'worldPos' should be a coordinate that has the right x and z positions, and a 'y' position guaranteed to be above the surface of the terrain. The raycast returns a normal (perpendicular) vector to the surface. The Quaternion.FromToRotation() will rotate the prefab to match the angle.
P.S. For future questions, please post your script. It gives us information like what language you are coding in, and it allows us to tailor the answer to your code.
I'm sorry about that. I've edited the question so it has the original script.
Since there is no Instantiate() in this code, that you are dealing with the section starting on line 40. Here is a bit of a untested rewrite of your code to add alignment:
if(Input.Get$$anonymous$$ouseButtonDown(0) && oCD.objectBelow == false )
{
Transform tr = currentlySelectedBuilding.transform;
RaycastHit hit;
// Aligns the building with the surface
if (Physics.Raycast (rounded$$anonymous$$ousePosition + 100 * Vector3.up, Vector3.down, out hit)) {
tr.rotation = Quaternion.FromToRotation(trrm.up, hit.normal) * tr.rotation;
}
//sets the building at the location it was at when mouse0 was hit.
currentlySelectedBuilding.transform.position = rounded$$anonymous$$ousePosition;
//I had to make the prefab ignore raycast hit while it was moving otherwise it interacts with the raycast and creates weird artefacts.
//since I now want the ray to detect it, I changed it back to obstacle.
currentlySelectedBuilding.layer = Layer$$anonymous$$ask.NameToLayer("Obstacle");
//this gets around the building(clone) problem.
currentlySelectedBuilding.name = buildingName;
//update graph so units avoid building
AstarPath.active.UpdateGraphs(currentlySelectedBuilding.collider.bounds);
//make the building and ocd script null.
currentlySelectedBuilding = null;
oCD = null;
}
Adjust the 100 as needed. If no other objects but the 'terrain' can be in teh way, you can use the more efficient Collider.Raycast().
Thanks a lot! There is no instantiate in the update because the object has already been instantiate via another method governed by another script. It's instantiated the entire time it follows the mouse around. However your code works wonderfully!
The only problem is that sometimes the y is a bit off (it will either be half submerged or completely under the terrain). I tried deleting my y code on line 22 but then it would instantiate on the zero coordinate when I build on terrain that drops below zero. $$anonymous$$aybe my mistake is having terrain that goes below the zero coordinate?
Not sure why your positioning is wrong, but an easy fix is to use the hit.point from the Raycast to do the positioning. If the pivot for your object is not at the bottom, you might have to the 'y'.
Line 12 and 22 make the y equivalent to the hitpoint y. So it's fine when I build on flat surfaces. When I build on slopes, the angles are great thanks to your code, but the y is sometimes not great. I'm guessing it's because the slopes are uneven so if I click a point that is lower than the surrounding points then the object will instantiate below the surface. If I find a way to get all the y points under the object and then instantiate on a y equal to the average of all those points, that might solve it. I just have no idea how I'd do that.