Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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.

Update: I got it working thanks to some answers, but the ball freaks out towards the far end of the plane. Why is this? https://www.dropbox.com/sh/9pqnl30lm6uwm6h/AACc2JcbW16z6PuHFLLfCAX6a

share|improve this question
    
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? –  nathan May 26 at 7:42
    
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 :| –  Discipol May 26 at 7:56
    
Could you try to create a minimal project as a test case and submit the relevant code snippet here? –  nathan May 26 at 8:12
    
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. –  Garan May 26 at 8:53
    
I will try your suggestion Garan then post the project, nathan, once I get home from work :D –  Discipol May 26 at 9:16

2 Answers 2

up vote 2 down vote accepted

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.

share|improve this answer
    
+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. –  bummzack May 26 at 13:30
    
the ball freaks out at the back of the plane, why? dropbox.com/sh/9pqnl30lm6uwm6h/AACc2JcbW16z6PuHFLLfCAX6a –  Discipol May 26 at 21:02
    
You don't appear to be using the code I suggested. If you're using both you can have issues. –  Byte56 May 27 at 0:27

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.