Search Unity

How to make drawable Paths

Discussion in 'Scripting' started by THHamiltonSmith, Jun 5, 2017.

  1. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    Hey! So I am making a Village/Kingdom simulation game but am having trouble on how to make paths that you can draw. So for example you click the path tool and draw a big path then draw a smaller path heading another angle. I also am not sure how to make buildings need to snap to the path so they arent in the middle of nowhere. I have my GUIScript and BuildManager script if you need them just tell me. Thanks in advance
     
  2. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    just bumping because I really need help
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,775
    path is a bit of an ambiguous word in the context of games.

    Like you could be talking about a graphical path, like a dirt path through a field. Well drawing such a path will have huge implications on the sort of way you setup your ground. Like is it a heightmap?

    Another path could be referring to a pathfinding path. Like a series of nodes/positions that an AI unit traverses across.

    And again, the specific of your implementation really effect how you resolve this issue.

    So be a little more specific.

    You sound like you might want the prior... since you're also talking about placing buildings down. And you want to do it at runtime... I ain't going to lie, that's not a trivial thing to do. You also sound very novice to programming, which is going to make the hurdle seem even higher.

    So try breaking this down into smaller steps, like... lets just start with drawing anything on your terrain. Then move onto drawing paths. Then move on to placing buildings.

    So first step... drawing something on your terrain. What's your terrain setup as? And do you know how to add paths at design time? How do you think you'll emulate the design time process at runtime?
     
  4. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    I understand what you mean. I dont get what you mean when you say what it is setup as though. I have already got placing buildings in the game and I have some placeable paths but arent very good as they dont snap perfectly
     
  5. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    816
    Nobody has idea what you mean by "path" yet, which seems rather essential to understanding your problem since its what your buildings are supposed to snap to.
     
  6. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    oml so sorry I forgot. By path I mean something Villagers can walk on. Honestly Im fine without pathfinding I just need a path drawing system.
     
  7. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    816
    Is the player able to make the path as wide as they wish?
     
  8. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    No there would be one width only. Length would be set in tiles
     
    Last edited: Jun 8, 2017
  9. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    -- This video has a path system that I would like to know how to make
     
  10. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    Just bumping
     
    Last edited: Jun 12, 2017
  11. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    265
    Hey there I'm the guy making Manor Lord lol. Actually to draw the path is extremely easy I simply paint the terrain. You basically just want to know the start and end points of your path.

    The important part here is to make sure each "tile" matches your splat map resolution perfectly. Once you got your start and end tiles it's easy to get the tiles on your terrain texture to paint. When I get home tonight I can get you the actual script for it :)
     
  12. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    OMG THANKS SO MUCH! I absolutly love Manor Lord and cant wait for a demo. Ive been following for a while and your my inspiration! I am very happy that there are people like you around :)
     
  13. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    265
    Hey no worries, actually do you need to just know how to pain the terrain between two points or you want to know how I made the green tiles as well?
     
  14. Cynikal

    Cynikal

    Joined:
    Oct 29, 2012
    Posts:
    122
    I'd be curious to know how you did the tile system / drawing on it (green tiles) and such.
     
  15. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    Both if you could :)
     
  16. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    265
    Alright here we go, I'll give you my reasoning behind my implementation and give you some code examples. This will be incomplete and it's on purpose, you're free to use the code but I implore you to not only copy/paste it. First because it won't work, and because that's a terrible way of learning.

    First thing we'll go over is clamping positions to your grid, since my game plays on a grid we need to make sure the positions we use fit within that grid:
    Alright, here's a view of how my world is setup:


    In my game each tile on the grid is 2x2 units. You could use any number in your case but my examples will assume that gridSize = 2. Here's the code I use to clamp positions to grid:
    Code (CSharp):
    1.     public static Vector3 ClampToGrid(Vector3 worldPos){
    2.        
    3.         int x = Mathf.FloorToInt(worldPos.x / gridSize);
    4.         int z = Mathf.FloorToInt(worldPos.z / gridSize);
    5.        
    6.         return new Vector3((x * gridSize) + 1f, worldPos.y, (z * gridSize) + 1f);
    7.     }

    That's great, but what happens if the Start position and end position of a path aren't perfectly aligned?
    Code (CSharp):
    1.     //This ensures that no matter where our mouse is, it will clamp to the nearest
    2.     //90 degree point. Imagine we're drawing a circle, one point is the center, and the other
    3.     //is somewhere on the edge. We want to get that point at exactly 90 degrees of the
    4.     //center, but at the same distance it's now.
    5.     private Vector3 GetClampedPosition(Vector3 start, Vector3 end){
    6.         //Get the distance from the begining to the end of our path
    7.         float distance = Vector3.Distance(start, end);
    8.         //Get the current angle between both points
    9.         Quaternion qRot = Quaternion.FromToRotation(Vector3.right, end - start);
    10.         Vector3 eul = qRot.eulerAngles;
    11.         //Here pathAngle = 90, you could have it to any other value you want to clamp to.
    12.         float angle = pathAngle * (int)Mathf.Round(eul.y / pathAngle);
    13.         //Here's where the magic happens, we're basically moving our end position along the circle arc to the nearest point
    14.         float xEnd = start.x + distance * Mathf.Cos(angle * (Mathf.PI / 180f));
    15.         float zEnd = start.z + distance * Mathf.Sin(-angle * (Mathf.PI / 180f));
    16.         return new Vector3(xEnd, groundLevel, zEnd);
    17.     }
    18.     //Once we have our correct final position, we want to get a list of all the positions between the start and end
    19.     private List<Vector3> GetPathFromTo(Vector3 from, Vector3 to){
    20.         List<Vector3> newList = new List<Vector3>();
    21.         //Used to determine the number of positions we're expecting
    22.         float lerpIndex = 0f;
    23.         //Very basic Pythagorian algorithm
    24.         float a = Mathf.Abs(from.x - to.x);
    25.         float b = Mathf.Abs(from.z - to.z);
    26.         float c = Mathf.Sqrt((a * a) + (b * b));
    27.         float lerpIncrease = 1f / (c / gridSize);
    28.         while(lerpIndex <= 1f){
    29.             Vector3 newVec = Vector3.Lerp(from, to, lerpIndex);  
    30.             //Here I'm clamping to grid just as a safety because Lerp could return something odd
    31.             newVec = TerrainHelper.ClampToGrid(newVec);
    32.             if(!newList.Contains(newVec)){
    33.                 newList.Add(newVec);
    34.             }  
    35.             lerpIndex += lerpIncrease;
    36.         }
    37.         return newList;
    38.     }

    Alright so now we're able to get a path, that is a list of all the positions between two points. You probably know where we're going with this, we're going to place the tiles along that path so we can visualize it. I won't show you any code for this I simply have a prefab of a green tile, I use an object pool to just place them on every position of my path, plain and simple. The tiles don't serve any logical purpose they're only for the user to see where the path is about to be placed.

    The next steps will be to paint our terrain, this only works if you're using the Unity terrain, I can't help if you're using a standard mesh or any other terrain. The first thing we'll need is to create a box colider and I'll explain why we need that later.
    Code (CSharp):
    1. public void SetStraightCollider(Vector3 from, Vector3 to, BoxCollider boxCol, float width = 1f){
    2.     Vector3 realEnd = GetClampedPosition(from, to);
    3.     float length = Vector3.Distance(from, realEnd);
    4.     //Here since we're using the middle of the tiles I want the collider to cover everything that's why we're increasing by gridSize
    5.     boxCol.size = new Vector3(length + gridSize, 4f, width);  
    6.     boxCol.center = new Vector3((length) / 2 , 0f, 0f);      
    7. }

    How does the terrain texture work exactly? It works using a splatmap. Imagine a texture that covers your entire terrain, that texture tells the graphic's card how to blend other textures. This texture has 4 channels, Red, Green, Blue and Alpha. Each channel is mapped to a specific texture, and the color values will tell the engine how to blend.

    This is where the collider comes in handy, I wrote a function that paints the area covered by a collider on my terrain:
    Code (CSharp):
    1. public static void makeChanges(GameObject go)
    2.     {
    3.         //Get the current terrainData
    4.         Terrain mainTerrain = Terrain.activeTerrain;
    5.         TerrainData terrainData = mainTerrain.terrainData;
    6.        
    7.         //Get the collider from the GameObject
    8.         BoxCollider boxCol = go.GetComponent<BoxCollider>();
    9.        
    10.         //Get both extremes corner, imagine your terrain is 0,0 to 1,1
    11.         //We want to get the corner closest to 0,0 and the one closest to 1,1
    12.         Vector3 worldPos1 = new Vector3(boxCol.bounds.min.x, go.transform.position.y, boxCol.bounds.min.z);
    13.         Vector3 worldPos2 = new Vector3(boxCol.bounds.max.x, go.transform.position.y, boxCol.bounds.max.z);
    14.        
    15.         //Here we want to get the position on the alphamap, remember my map is 1024 by 1024
    16.         Vector3 pos1 = getTerrainRelativeAlphaPosition(worldPos1);
    17.         Vector3 pos2 = getTerrainRelativeAlphaPosition(worldPos2);
    18.        
    19.         //We need to convert them to int because you can only access whole pixels
    20.         int x1 = (int)pos1.x;
    21.         int y1 = (int)pos1.y;
    22.         int x2 = (int)pos2.x;
    23.         int y2 = (int)pos2.y;
    24.        
    25.         //Get the X and Y value closest to your origin(0,0)
    26.         int originX = Mathf.Min(x1, x2);
    27.         int originY = Mathf.Min (y1, y2);
    28.        
    29.         //Get the width and length of the area we want to paint.
    30.         int width = Mathf.Abs(x2 - x1);
    31.         int height = Mathf.Abs(y2 - y1);
    32.        
    33.         //Since our paths are straight, one of these will be 0 so we want to clamp to at least 1
    34.         width = Mathf.Clamp(width, 1, int.MaxValue);
    35.         height = Mathf.Clamp(height, 1, int.MaxValue);
    36.                
    37.         //Get the region of the terrain we're interested in      
    38.         float[, ,] alphas = terrainData.GetAlphamaps(originX, originY, width, height);
    39.        
    40.         //Set them to your desired weights
    41.         //Here index 1 is my dirt texture and 0 is my grass
    42.         //I'm telling the terrain to make that tile 80% dirt and 20% grass
    43.         for(int i = 0 ; i < height ; i++)
    44.         {
    45.             for(int j = 0 ; j < width ; j++)
    46.             {
    47.                 //4 textures used on my terrain the weights must always = 1
    48.                 alphas[i, j, 1] = 0.8f;
    49.                 alphas[i, j, 0] = 0.2f;
    50.                 alphas[i, j, 2] = 0.0f;
    51.                 alphas[i, j, 3] = 0.0f;
    52.             }
    53.         }
    54.         //Finally we set the alphamap back to the terrain
    55.         //Be careful this operation is somewhat slow
    56.         terrainData.SetAlphamaps(originX, originY, alphas);
    57.     }
    58.  
    59.     public static Vector3 getTerrainRelativeAlphaPosition(Vector3 position)
    60.     {
    61.         Terrain terrain = Terrain.activeTerrain;
    62.         //Remove any offset in case your terrain is not at 0,0,0
    63.         Vector3 tempCoord = position - terrain.gameObject.transform.position;
    64.         Vector3 coord;
    65.        
    66.         //Get the normalized position (between 0 and 1)
    67.         coord.x = tempCoord.x / terrain.terrainData.size.x;
    68.         coord.y = tempCoord.y / terrain.terrainData.size.y;
    69.         coord.z = tempCoord.z / terrain.terrainData.size.z;
    70.        
    71.         //Convert back to the alphamap size
    72.         int hmWidth = terrain.terrainData.alphamapWidth;
    73.         int hmHeight = terrain.terrainData.alphamapHeight;      
    74.         float posXinTerrain = coord.x * hmWidth;
    75.         float posZinTerrain = coord.z * hmHeight;
    76.                
    77.         return new Vector3(posXinTerrain, posZinTerrain);
    78.     }
    79.  

    So there it is, note that this really only applies if your paths are complete straight (clamped to 90 degrees angle).

    Let me know if you have any other questions and good luck!

    Hugo
     
  17. THHamiltonSmith

    THHamiltonSmith

    Joined:
    Mar 5, 2017
    Posts:
    128
    Thanks! I have 1 more question. What do I need to add to make the scripts work? Im not certain what I must add and if you could help that would make me so happy. My last question is when Manor Lord will be up for a Beta realease for people to play so I can record it on youtube :)
     
    Last edited: Jun 16, 2017