Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Bug Grid-based cursor does not work

Discussion in 'Scripting' started by kineticcrusher, Jun 11, 2020.

  1. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    I have a cube GameObject set up to follow the cursor, and as of right now, I can get it to draw on the frame and follow the cursor just fine, but my attempts to make it snap to the grid have not worked. Why is this?

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CursorScript : MonoBehaviour
    4. {
    5.     public GameObject cursor;
    6.  
    7.     public Camera cam;
    8.  
    9.     private void Update()
    10.     {
    11.         int mousePosX = Mathf.RoundToInt(Input.mousePosition.x);
    12.         int mousePosY = Mathf.RoundToInt(Input.mousePosition.y);
    13.  
    14.         cursor.transform.position = cam.ScreenToWorldPoint(new Vector3(mousePosX, mousePosY, 1));
    15.     }
    16. }
     
  2. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Input.mouseposition.x and *.y are already ints, since you can't really break down pixels into smaller bits.

    You'd need to divide by, say 10, round that and multiply it by 10 again, which would give you a grid 10*10 fields.
     
    kineticcrusher likes this.
  3. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    This works to an extent, but (my game is based off of an array of maptiles and the height and width of that map is customizeable) when I use the height and width of the map as the multiplicative/divisor, it doesn't line up at all and the cursor's increments of movement are way off.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CursorScript : MonoBehaviour
    4. {
    5.     public GameObject cursor;
    6.     public GameObject map_gen;
    7.  
    8.     public Camera cam;
    9.  
    10.     private void Update()
    11.     {
    12.         map_gen = GameObject.Find("map_gen");
    13.         MapGenerator map_generator = map_gen.GetComponent<MapGenerator>();
    14.         int mousePosX = Mathf.RoundToInt(Input.mousePosition.x) / map_generator.width * map_generator.width;
    15.         int mousePosY = Mathf.RoundToInt(Input.mousePosition.y) / map_generator.height * map_generator.height;
    16.  
    17.         cursor.transform.position = cam.ScreenToWorldPoint(new Vector3(mousePosX, mousePosY, 1));
    18.     }
    19. }
     
  4. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Your math is wrong like that. You need to divide, round, multiply. You are currently not rounding anything but the mousePosition, which, as I said, is already an int. It should look like this:

    Code (CSharp):
    1. int mousePosX = Mathf.RoundToInt(Input.mousePosition.x / map_generator.width) * map_generator.width;
    2.  
     
    kineticcrusher likes this.
  5. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    Still does the exact same thing.

    Code (CSharp):
    1. private void Update()
    2.     {
    3.         map_gen = GameObject.Find("map_gen");
    4.         MapGenerator map_generator = map_gen.GetComponent<MapGenerator>();
    5.         int mousePosX = Mathf.RoundToInt(Input.mousePosition.x / map_generator.width) * map_generator.width;
    6.         int mousePosY = Mathf.RoundToInt(Input.mousePosition.y / map_generator.height) * map_generator.height;
    7.  
    8.         cursor.transform.position = cam.ScreenToWorldPoint(new Vector3(mousePosX, mousePosY, 1));
    9.     }
     
  6. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    It works fine for me. Is the map_generator.height and width the size of your grid tiles? Because that's what they should be. If they are still misaligned after that, you'll need to add an offset to account for that.

    If all that fails, you might need to give me a bit more information on your project.
     
    kineticcrusher likes this.
  7. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    The height and width numbers determine the dimensions of the map; and the map is composed of tiles (square GameObjects). Each tile is 1x1x1.
     
  8. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Are you using 2D or §D?
    Are you looking on the tiles straight down or at an angle?
    Is the cursor supposed to be on the canvas or in worldspace?`

    Come on, work with me here.
     
    kineticcrusher likes this.
  9. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    3D, but the camera is looking straight down on the tiles from an orthographic perspective, and the cursor is in the worldspace -- it's not GUI but instead a literal cube that will move with the cursor.
     
  10. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Ok, and the tiles are also gameobjects in the world?

    Then you probably want to align the cursor with the tiles. In that case, it's probably easier to raycast and work with the tile. Something like this:
    - raycast to get the tile the mouse is over
    - get the position that tile is at
    - add some y offset to get the cube above the tile
    - put the cube to that position.

    Once you add stuff to the tiles, it might be necessary to use a layermask to only raycast to the tiles.
     
    kineticcrusher likes this.
  11. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    This puts the cursor object at the position of the object the mouse is over and moves it up 1 unit, so it's above the tile. You can adjust that to your liking.

    Code (CSharp):
    1.  
    2. public class CursorScript : MonoBehaviour
    3. {
    4.     public GameObject cursor;
    5.     private void Update()
    6.     {
    7.     RaycastHit hit;
    8.  
    9.         if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
    10.         {
    11.             cursor.transform.position = hit.position + new Vector3(0,1,0);
    12.         }
    13.     }
    14.  
     
    kineticcrusher likes this.
  12. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    Around the time you posted this, I had just finished my attempt at writing it, and it appears to work -- with one tiny exception.

    When I drag the cursor around on the map, it flicks back and forth (probably every frame) from being in the grid-snapped position, and being in the float position it takes from the cursor.

    Code (CSharp):
    1. public class CursorScript : MonoBehaviour
    2. {
    3.     public GameObject cursor;
    4.     public GameObject map_gen;
    5.  
    6.     public Camera cam;
    7.  
    8.     RaycastHit hit;
    9.  
    10.     public float raycastLength = 1.5f;
    11.  
    12.     private void Update()
    13.     {
    14.         MapGenerator map_generator = map_gen.GetComponent<MapGenerator>();
    15.         float mousePosX = Input.mousePosition.x;
    16.         float mousePosY = Input.mousePosition.y;
    17.  
    18.         cursor.transform.position = cam.ScreenToWorldPoint(new Vector3(mousePosX, mousePosY, 2));
    19.  
    20.         if (Physics.Raycast(cursor.transform.position, Vector3.down, out hit, Mathf.Infinity) && hit.transform.tag == "map")
    21.         {
    22.             cursor.transform.position = new Vector3(hit.transform.position.x, 1, hit.transform.position.z);
    23.         }
    24.     }
    25. }
     
  13. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    You should only have the raycast, not give it positions any other way. If you still have parts of your code from above, that will definitely happen.

    Another reason could be that there are gaps between the tiles, so the raycast doesn't hit anything. In that case, you can add an if statement to say if(hit != null) before giving the cursor a new position, to check if it actually is getting a new position.
     
  14. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    Well, if I were to remove the 'cursor.transform.position = cam' line, then it wouldn't be moving with the cursor in the first place, and as such would have no way of knowing if a tile is under it.
     
  15. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    No. That line is trying to move the cube to the exact position that the cursor is pointing at in the world. But you don't want the cube to be there. Not ever.

    The raycast is drawing a line from the camera along the mouse position into the game world. It reports the first gameobject it hits in the "hit" object. In this case, that's going to be a tile. It then uses the position of that tile to calculate a position for the cursor cube.

    That's all the cube needs to get its position.

    Adding another way of doing that will not help, it will just give you the random flickers you see, because you set it's position to the cursor position, then the tile position then the cursor position, and so forth.
     
    kineticcrusher likes this.
  16. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    This makes sense, but the ray is coming out of the cursor object, not the mouse cursor. I tried creating a new Vector3 with the X and Y coordinates of the mouse position, but it didn't do anything.
     
  17. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Look at the way I wrote my raycast. You need to cast from the main camera (or the camera you use, in your case cam, which is still most likely the same camera) through the mouse cursor. The object you call cursor in your program is not the mouse cursor but the cube you want to move. You get the mouse position with Input.mousePosition.
     
    kineticcrusher likes this.
  18. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    I apologize for not reading the snippet fully. This is probably my last inquiry:

    This code returns "Object reference not set to instance of an object".
    Code (CSharp):
    1. public class CursorScript : MonoBehaviour
    2. {
    3.     public GameObject cursor;
    4.  
    5.     RaycastHit hit;
    6.  
    7.     private void Update()
    8.     {
    9.         if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit) && hit.transform.tag == "map")
    10.         {
    11.             cursor.transform.position = new Vector3(hit.transform.position.x, 1, hit.transform.position.z);
    12.         }
    13.     }
    14. }
    15.  
     
  19. kineticcrusher

    kineticcrusher

    Joined:
    Aug 17, 2019
    Posts:
    27
    Issue solved; I had to replace the main camera earlier and forgot to assign it the correct tag.

    Thanks for all the help; I really appreciate it! You've helped me surmount lots of problems and this is probably gonna help me a lot in the future.
     
  20. MatrixQ

    MatrixQ

    Joined:
    May 16, 2020
    Posts:
    87
    Glad we could figure this out.

    One word of advice for the future:
    When you post code that gives you an error message, post the entire error message. The message shows the line the error is in, so people know where to look to help you. It makes things a lot easier.