4
\$\begingroup\$

I am trying to make a tiled landscape. The terrain itself is not made from tiles but the world has a grid which I define.

I would like to place boxes/rectangles which snap to this grid, at runtime, but in order for me to do that I must get a projection from the user screen to the real-world coordinates.

I have tried various examples using the Ray class but nothing worked. It compiles and outputs a constant value no matter where I put the mouse. I have tried to add some tiles and try to detect them but no luck. I also tried with one plane as big as my world but still no luck.

I am using C# but even a JS version would be helpful.

This technique involves calculating which tile the mouse is under by the x and y positions. Perhaps detecting which tile itself is being pointed to would be a simpler task, at which point I can just retrieve its i/j properties.

\$\endgroup\$
6
  • \$\begingroup\$ This is hard to help since we don't really know what you have tried. I have done similar things quite sometimes. Have you tried Camera.ScreenPointToRay? \$\endgroup\$
    – nathan
    May 26, 2014 at 7:42
  • \$\begingroup\$ Yes. I have Googled all weekend and tried all the snippets of code I could get my hands on, most from stackexchange. Nothing worked, just got a constant Vector3. I assume I am the one doing (or not doing) something. But I don't know what I don't know :| \$\endgroup\$
    – Discipol
    May 26, 2014 at 7:56
  • \$\begingroup\$ Could you try to create a minimal project as a test case and submit the relevant code snippet here? \$\endgroup\$
    – nathan
    May 26, 2014 at 8:12
  • \$\begingroup\$ One of the important things for raycasting in Unity is that the target MUST have a collider attached. Try adding a collider to the tiles and see if that works. \$\endgroup\$ May 26, 2014 at 8:53
  • \$\begingroup\$ I will try your suggestion Garan then post the project, nathan, once I get home from work :D \$\endgroup\$
    – Discipol
    May 26, 2014 at 9:16

2 Answers 2

8
\$\begingroup\$

Instead of using a collider for your terrain, you can also use a plane to perform a raycast. For example, if your grid is situated on the XZ plane, you can do something like this:

static Plane XZPlane = new Plane(Vector3.up, Vector3.zero);

public static Vector3 GetMousePositionOnXZPlane() {
    float distance;
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    if(XZPlane.Raycast (ray, out distance)) {
        Vector3 hitPoint = ray.GetPoint(distance);
        //Just double check to ensure the y position is exactly zero
        hitPoint.y = 0;
        return hitPoint;
    }
    return Vector3.zero;
}

This will return a 3D world position of where the mouse intersects with the XZ plane. You can then round the x and z values to the nearest integer value to have a snapping effect when placing your objects. Similar to how Bummzack does in his code:

position.x = Mathf.Round(position.x / GridSize) * GridSize;
position.z = Mathf.Round(position.z / GridSize) * GridSize;

Using the plane method ensures you don't have to change the size or position of your terrain collider when moving around your world.

\$\endgroup\$
1
  • \$\begingroup\$ +1. Neat, I've never used Plane before. In case of a flat world/grid that covers the entire screen this is the better approach. \$\endgroup\$
    – bummzack
    May 26, 2014 at 13:30
3
\$\begingroup\$

First of all, you need to attach a collider to your terrain in order to get hits from raycasts. If you're using a Terrain, it should already have a Terrain Collider attached to it.

In your script you can then do something like this:

// create a ray from your mouse-position
Ray r = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(r, out hit)){
    // if the ray hit a collider, we'll get the world-coordinate here.
    Vector3 worldPos = hit.point;

    // round coordinates to grid-size (GridSize could be a constant or a
    // public class member that you can change in the Inspector).
    worldPos.x = Mathf.Round(worldPos.x / GridSize) * GridSize;
    worldPos.z = Mathf.Round(worldPos.z / GridSize) * GridSize;
    // you can do the same for the y-coordinate, if you need grid
    // snapping in the y-axis.

    // position your object at the calculated position:
    MyObject.transform.position = worldPos;
}

From your question I assumed that your grid only applies to the X & Z axes. Of course you could also apply it to the Y axis or leave the Y coordinate untouched (which will position the object at the Y position of the ray/terrain intersection). If you have a terrain with elevation, this might not lead to the desired results and you'll have to cast another ray downwards at the new position (eg. the one that lies on your X/Z grid) to get the exact Y-coordinate.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .