Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Building roads in a 2D tilemap based game

Discussion in '2D' started by Portyx, Aug 18, 2021.

  1. Portyx

    Portyx

    Joined:
    Sep 16, 2018
    Posts:
    2
    Hi there, I'm working on a small 2D game where you can build roads, factories, houses etc. on a large tilemap. Most of it works already but I got a problem with the roads. I'm using a LineRenderer to "draw" roads from one tile to another (using the centre point in world coordinates) while the mouse button is down and moves from one tile to another.

    The problem is, unless I exactly hit the corner pixel, my road is not painted diagonal but in a 90° angle. I understand why:

    1 2 3
    4 5 6
    7 8 9

    When the mouse is dragged from tile 1 to 5, it draws a diagonal line, but usually when you try to drag from 1 to 5, you will either pass through 2 or 4 so it draws 1-4 and 4-5 or 1-2 and 2-5.

    I thought about only checking the mouse position every X milliseconds instead of in each update-call, but

    a) I'm not sure how - I thought about trying it with InvokeRepeating but that seems a bit oversized and I would have to redo most of my update logic.

    b) I'm not sure what a good X would be to make it feel sufficiently smooth. I'd like to avoid the case where a player drags the mouse to a new tile and has to wait for some time until the road is finally drawn.

    Anybody did something similiar before and has a good idea how to solve this?

    Thanks.
     
  2. Redlar

    Redlar

    Joined:
    Aug 17, 2016
    Posts:
    16
    I'm sort of doing something similar with a square tilemap, but I don't make diagonals, only straight-line segments, so my ideas might not be suited for your project.

    You could run some code in LateUpdate() or FixedUpdate() so that only location variables within a specific range, say + - 0.25 of the X/Y value, will be rounded down to get the location of the next tile in your road. That should make it so that just slight clipping of non-diagonal tiles won't be registered, and as long as players are expected to move the mouse to the center of the tile they shouldn't have any issues with the responsiveness since it checks once per frame.

    If you have an animated cursor you could run the position check as an Animation Event, which is what I'm using with my tilepainter function. The game only checks the cursor position if a move button is pressed, once when first moved and once every 15 frames of the animation, with a 2-frame delay if the calling method is not the animation event so that the cursor doesn't do a double-move over a tile. I suppose for your mouse-based tilepainter you'd have it check the current location of the mouse with the first click, then you could check the location with an Animation Event as it's being dragged, rounding down as above to avoid adding non-diagonal tiles to your road.

    Code (CSharp):
    1.    public void Keypress()
    2.     {
    3.         if (!move)
    4.         {
    5.             if (Input.GetKey(KeyCode.W))
    6.             {
    7.                 Debug.Log(currentState);
    8.                 v2c.y += 1;
    9.                 MoveCursor();
    10.             }
    11.             if (Input.GetKey(KeyCode.A))
    12.             {
    13.                 v2c.x -= 1;
    14.                 MoveCursor();
    15.             }
    16.             if (Input.GetKey(KeyCode.S))
    17.             {
    18.                 v2c.y -= 1;
    19.                 MoveCursor();
    20.             }
    21.             if (Input.GetKey(KeyCode.D))
    22.             {
    23.                 v2c.x += 1;
    24.                 MoveCursor();
    25.  
    26.             }
    27.         }
    28.     }
    29.  
    30.     public void MoveCursor()
    31.     {
    32.         if (v2c.x > maxX)
    33.         {
    34.             v2c.x = minX;
    35.         }
    36.         else if (v2c.x < minX)
    37.         {
    38.             v2c.x = maxX;
    39.         }
    40.         else if (v2c.y < minY)
    41.         {
    42.             v2c.y = maxY;
    43.         }
    44.         else if (v2c.y > maxY)
    45.         {
    46.             v2c.y = minY;
    47.         }
    48.  
    49.         if (v2c.x >= minX && v2c.x <= maxX && v2c.y >= minY && v2c.y <= maxY)
    50.         {
    51.             cursor.transform.position = v2c;
    52.             StartCoroutine(MoveTimer());
    53.         }
    54.     }
    55.  
    56.     IEnumerator MoveTimer()
    57.     {
    58.         move = true;
    59.         yield return 2;
    60.         //Debug.Log("COROUTINE THIS! *PEW* ");
    61.         move = false;
    62.     }
     
  3. Portyx

    Portyx

    Joined:
    Sep 16, 2018
    Posts:
    2
    Hi Redlar,

    thanks for your reply. The problem is that I want to allow diagonal AND horizontal / vertical roads.

    I tried around a bit more and this is as close as I could get by now:

    Code (CSharp):
    1.  
    2. void Update()
    3.     {
    4.         if (Input.GetMouseButtonDown(0))
    5.             CreateRoad();
    6.  
    7.         if (Input.GetMouseButton(0))
    8.         {
    9.             Vector2 mousePosition = Mouse.GetMouseWorldPosition();
    10.             if (mOrigin == null)
    11.                 mOrigin = mGrid.GetValue(MousePositions[MousePositions.Count - 1]);
    12.  
    13.             if (mGrid.GetValue(mousePosition) != mOrigin)
    14.                 mFrameCounter++;
    15.  
    16.             if(mFrameCounter >= 50)
    17.             {
    18.                 List<PathNode> neighbours = mGameHandler.GameGrid.GetNeighbours(mOrigin);
    19.                 if (neighbours.Contains(mGrid.GetValue(mousePosition)))
    20.                 {
    21.                     UpdateRoad(mousePosition);
    22.                     mOrigin = null;
    23.                     mFrameCounter = 0;
    24.                 }
    25.             }              
    26.         }
    27.     }
    It works quite alright like this, what disturbs me is the constant 50 here because I don't know if that is appropriate for all users. I wonder if there is a better way to detect if the mouse has been over a different tile than the previous one for a long enough time.
     
  4. Redlar

    Redlar

    Joined:
    Aug 17, 2016
    Posts:
    16
    Why wouldn't this work with rounding the X and Y values within a range? If the mouseposition is (0,0), then you drag it up and to the right (1.0,1.0), as long as the value of X and Y is under 1.25 for both you can round down to the correct tile position. If the value is outside the acceptable range, say (1.5, 1.75), when the position is checked again, you can round it to a horizontal or vertical movement.

    Instead of using mFrameCounter with Update(), it might be better to use it with FixedUpdate() or Time.fixedDeltaTime. If the cursor is animated, you can just attach the animation event to frames in the animation instead of setting it to a constant time , so the player can visually see that the cursor cycled through the animation and that should be a long enough time for it to register the mouse position.