Search Unity

Education Unity 2D Map/Board Implementation using a Grid (Tilemap)

Discussion in 'Projects In Progress' started by panayiotismilios, Jun 25, 2022.

  1. panayiotismilios

    panayiotismilios

    Joined:
    Jun 21, 2018
    Posts:
    8
    Hello,

    I've made a complete implementation for a 2D map or board and i would like to share in case it helps anyone.
    To do this, you'll need to be working on a TileMap.

    First you need to attach to your Grid a 2D Box Collider, because all actions on the map are implemented using OnMouse() methods. Make sure, the size fits your entire TileMap (ScreenShot 1)

    I want to be able to drag the map using Left-Mouse click but i also want to be able to click the tiles so i can focus on them. I'm gonna use OnMouseDown() to click the tiles and OnMouseDrag() to move over the map.

    Let's focus on dragging first.

    OnMouseDrag() is called as soon as left-click is pressed. The most basic dragging implementation is as follows.

    Code (CSharp):
    1.  
    2. public void OnMouseDrag()
    3. {
    4.             var dX = Input.GetAxis("Mouse X") * Time.deltaTime;
    5.             var newXPos = mainCamera.transform.position.x - dX;
    6.  
    7.             var dY = Input.GetAxis("Mouse Y") * Time.deltaTime;
    8.             var newYPos = mainCamera.transform.position.y - dY;
    9.  
    10.             mainCamera.transform.position = new Vector3(newXPos, newYPos, -10);
    11. }
    12.  
    But this implementation comes with a lot of issues as you'll find out as you start testing your dragging mechanism. Simply clicking on the map could cause a dragging effect. That's definitely not a desired behavior as we need to give our player the ability to click and focus on a specific tile and we won't be able to distinguish between dragging and clicking.

    (OnMouseDrag() and OnMouseDown() are both called when the left-click is pushed down)

    To counter that effect, we need to add a delay on the Drag method.

    Code (CSharp):
    1. public IEnumerator OnMouseDrag()
    2. {
    3.         yield return new WaitForSeconds(0.075f);
    4.      
    5.             var dX = Input.GetAxis("Mouse X") * Time.deltaTime;
    6.             var newXPos = mainCamera.transform.position.x - dX;
    7.  
    8.             var dY = Input.GetAxis("Mouse Y") * Time.deltaTime;
    9.             var newYPos = mainCamera.transform.position.y - dY;
    10.  
    11.             mainCamera.transform.position = new Vector3(newXPos, newYPos, -10);
    12.             yield return null;
    13. }
    14.  
    This implementation is fine for the most part, but you'll find out that there is a stuttering effect and sometimes the camera jumps to a position that's not supposed to. (If anyone cares to provide a detailed explanation to why it happens, i'd be grateful.)

    To fix that issue, i add to the deltaTime the delay i've added to the method. That makes the dragging implementation a lot smoother and the stuttering effect is almost entirely gone.

    Code (CSharp):
    1.     public IEnumerator OnMouseDrag()
    2.     {    
    3.         yield return new WaitForSeconds(0.075f);
    4.  
    5.             var dX = Input.GetAxis("Mouse X") * (0.075f + Time.deltaTime);
    6.             var newXPos = mainCamera.transform.position.x - dX;
    7.  
    8.             var dY = Input.GetAxis("Mouse Y") * (0.075f + Time.deltaTime);
    9.             var newYPos = mainCamera.transform.position.y - dY;
    10.  
    11.             mainCamera.transform.position = new Vector3(newXPos, newYPos, -10);
    12.         yield return null;
    13.     }
    Next, we need to create the tile click implementation.When the player presses the left button, we're not yet certain whether he wants to drag or click. But how do we understand the difference? The difference relies on two things. The camera and the OnMouseUp() method. Like i said, OnMouseDown() and OnMouseDrag() both are called when left-click is pushed down, so in every case OnMouseUp will be called eventually. But when the player clicks, the camera stays in the same position.

    This is the most important piece of information because now we know how to distinguish between click and drag. So we will record the cameraPosition during the OnMouseDown() and then we'll compare it in OnMouseUp().

    Code (CSharp):
    1. public void OnMouseDown()
    2.     {
    3.         cameraPositionBefore = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));
    4.     }
    Code (CSharp):
    1. public void OnMouseUp()
    2.     {
    3.         cameraPositionAfter = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));
    4.  
    5.         if (cameraPositionAfter == cameraPositionBefore)
    6.         {
    7.             //Get Cellposition
    8.             var cellPosition = <Insert Your TileMap variable>.WorldToCell(cameraPositionAfter);
    9.             // Highlight your Tile here
    10.         }
    11.      
    12.     }
    To add a bit more Quality of Life to this whole implementation, we'll add the following features.
    • Dragging won't start if the mouse is over UI
    • Dragging will keep going if mouse is over UI
    • Clicking on tiles won't be possible if mouse is over UI
    To do the first two, we'll add a boolean variable to our OnMouseDown() method. To do the final bullet, we'll just add an extra check in our OnMouseUp() method.

    Code (CSharp):
    1.     public void OnMouseDown()
    2.     {
    3.         cameraPositionBefore = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));
    4.         if (!EventSystem.current.IsPointerOverGameObject())
    5.         {
    6.             is_dragging = true;
    7.         }
    8.     }

    Code (CSharp):
    1.     public IEnumerator OnMouseDrag()
    2.     {
    3.         yield return new WaitForSeconds(0.075f);
    4.         if (is_dragging)
    5.         {
    6.             var dX = Input.GetAxis("Mouse X") * (0.075f + Time.deltaTime) * speed; //Adjust speed of drag
    7.             var newXPos = mainCamera.transform.position.x - dX;
    8.  
    9.             var dY = Input.GetAxis("Mouse Y") * (0.075f + Time.deltaTime) * speed;
    10.             var newYPos = mainCamera.transform.position.y - dY;
    11.  
    12.             mainCamera.transform.position = new Vector3(newXPos, newYPos, -10);
    13.             yield return null;
    14.         }
    15.         yield return null;
    16.     }
    Code (CSharp):
    1.  public void OnMouseUp()
    2.     {
    3.         cameraPositionAfter = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));
    4.  
    5.         if (!EventSystem.current.IsPointerOverGameObject() && cameraPositionAfter == cameraPositionBefore)
    6.         {
    7.             //Get Cell Position
    8.             var cellPosition = <Insert Your Tilemap Here>.WorldToCell(cameraPositionAfter);
    9.            // Highlight your Tile here
    10.         }
    11.         is_dragging = false;
    12.     }
    And i think that's all. Attach this script to your Grid and you're good to go. I really hope this helps someone.
     

    Attached Files:

    Last edited: Jun 25, 2022