|
Post by wyliam on Apr 7, 2015 14:34:02 GMT
Curious if anyone's tried to do territory borders like the Civ series? I was thinking of overloading the markers, but that seems like a poor/limited approach. I'd even be open to an asset off the Unity store if it existed! The image below looks like it uses a depth shader... Unity Forum discussion...
|
|
|
Post by kenamis on Sept 23, 2015 0:42:22 GMT
Here's an example of what i've done so far.
|
|
|
Post by wyliam on Sept 23, 2015 1:19:50 GMT
Wow, that looks really good! Any hints/code suggesting how you did it?
|
|
|
Post by kenamis on Sept 23, 2015 6:06:34 GMT
It's very similar to the roads code. You'll need to create some new markers, one for each of the possible border scenarios for a single hex. Luckily, you can take advantage of rotation symmetry to save some space. You'll also need to pass in more information to the shader for a borders layer, so that you can have borders and roads. Let me know if you need more direction/advice. If there's enough demand I might consider making an asset, but that'd take some work to clean up and make more user friendly.
|
|
|
Post by kramar on Sept 23, 2015 11:55:25 GMT
Nice work! I'm going to have to do something like this as well. Any chance you could share what you've done?
|
|
|
Post by ftlmars on Nov 6, 2015 20:40:47 GMT
it would be nice if provide your shader changes example and how you managed to deal without having 6^6 markers (each of the possible border scenarios for a single hex)?
|
|
|
Post by kenamis on Nov 9, 2015 20:56:27 GMT
Well I'm hesitant to post any code because it will be specific to my game scenario and I don't have the resources to support the code for others. I can give you advice though. Also, I may post the small changes to the shader code that are necessary if Khash doesn't mind. Read through the roads example, the border system is almost exactly like it. Once you realize that roads are just borders through the center of the hex instead of the edges. The marker system already has rotation data that is passed to the shader and that is all that is needed to take advantage of rotation symmetry. Here is a shot from my HexSelectors.psd file of the borders, so you should only need 13 different markers.
|
|
|
Post by khash on Nov 10, 2015 10:54:13 GMT
great job!
|
|
|
Post by ftlmars on Nov 11, 2015 7:08:42 GMT
kenamis , thanks for advice I got your idea. But again shader need some changes, deal with it will not be easy for me. So again If you can share your shader changes =)
|
|
|
Post by ftlmars on Nov 15, 2015 12:03:36 GMT
done with shader by adding new MarkersPositionData texture and new hex type wich is passed via one of 4 new (RGBA) channel (new MarkersPositionData )
|
|
|
Post by kenamis on Nov 18, 2015 4:57:28 GMT
good job. I think you don't need to add new position data if you want to use one of the existing layers. That's what I did.
Thinking back now, for the borders you don't need to make any changes to the shader. The only changes needed would be for dynamic border colors. For that you need to take advantage of one of the other data slots that Khash left open.
I believe by default there are two unused indexes in the image that is passed to the shader. The first two indexes passes position and rotation data. I used all 4 layers (RGBA) to pass on the color data, but then only one layer can have the dynamic color. That's all I cared about but if you want to try to encode color into each of the RGBA channels to correspond to the 4 default layers you could. Then in the shader you can use that data to modify the marker on the layer in question. When I return from my trip I can post example of that shader code additions.
I know there is a lot there, let me know if any is unclear.
|
|
|
Post by ftlmars on Nov 18, 2015 9:10:54 GMT
good job. I think you don't need to add new position data if you want to use one of the existing layers. That's what I did. Thinking back now, for the borders you don't need to make any changes to the shader. The only changes needed would be for dynamic border colors. For that you need to take advantage of one of the other data slots that Khash left open. I believe by default there are two unused indexes in the image that is passed to the shader. The first two indexes passes position and rotation data. I used all 4 layers (RGBA) to pass on the color data, but then only one layer can have the dynamic color. That's all I cared about but if you want to try to encode color into each of the RGBA channels to correspond to the 4 default layers you could. Then in the shader you can use that data to modify the marker on the layer in question. When I return from my trip I can post example of that shader code additions. I know there is a lot there, let me know if any is unclear. kenamis, i added new position data because i want border to be separeted layer. By saying data slots you mean 4 layers (RGBA) maybe i missed something?
|
|
|
Post by kenamis on Nov 18, 2015 16:21:18 GMT
here, I was able to pull one of the changes to the hexmarkers class to show.
public void SetMarkerType(Vector3i position, Layer layer, float markerRotation, int iType, Color color = default(Color)) { //convert position to texture space index int x = position.x * dataSize; int y = position.y * dataSize; if (x < 0) { x = textureMapSize * dataSize + x; } if (y < 0) { y = textureMapSize * dataSize + y; } Color c = hexData.GetPixel(x, y); Color rotations = hexData.GetPixel(x + 1, y); Color colors = hexData.GetPixel (x, y + 1); //for border colors switch (layer) { case Layer.Borders: c.r = (float)iType / markerTypeCount; rotations.r = markerRotation; if(color != default(Color)) { //colors.r = (color.r * 1024.0f * 32.0f) + (color.g * 32.0f * 32.0f) + (color.b * 32.0f); //for border colors colors = color; } break; case Layer.Friendly: c.g = (float)iType / markerTypeCount; rotations.g = markerRotation; break; case Layer.Movement: c.b = (float)iType / markerTypeCount; rotations.b = markerRotation; break; case Layer.Grid: c.a = (float)iType / markerTypeCount; rotations.a = markerRotation; break; } hexData.SetPixel(x, y, c); hexData.SetPixel(x + 1, y, rotations); hexData.SetPixel (x, y + 1, colors);//for border colors dirty = true; }
so note the hexData variable. Its Texture2D type and the default dataSize is 2. Since it's 2 dimensional, 2x2=4 means we have 4 data slotts per hexposition to work with. The first two slots are used for position and rotation, I use the 3rd slot for color. hexData.SetPixel(x+1, y+1) is technically still unused. You could also increase the dataSize to pass more information if you needed.
here are the changes in the terrain shader
void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); float dataResolution = _MarkerSettings.z; int dataSize = round( _MarkerSettings.w); float2 pos = float2(IN.worldPos.x, IN.worldPos.z); Vector3i v = GetHexCoord(pos); float trueDataResolution = dataSize*dataResolution; float2 dataUV = float2( (v.x*dataSize+0.5) /trueDataResolution, (v.y*dataSize+0.5)/trueDataResolution); float2 data2UV = dataUV + float2(1.0/trueDataResolution, 0); float2 data3UV = dataUV + float2( 0, 1.0/trueDataResolution);//for border colors
float4 data = tex2D (_MarkersPositionData, dataUV); float4 data2 = tex2D (_MarkersPositionData, data2UV); float4 data3 = tex2D (_MarkersPositionData, data3UV);//for border colors
float2 markerUV; half4 marker; float2 markerCorner; const float TWO_PI = 6.28;
// 1st marker layer int type = round(data.r * _MarkerSettings.x * _MarkerSettings.y); if (type != 0) { float2 markerPoint = float2(v.uv.x, v.uv.y) - float2(0.5, 0.5); //Short way of 2d rotation matrix float angle = TWO_PI * data2.r; float cosRot = cos(angle); float sinRot = sin(angle); float2 singleMarkerUV = float2(markerPoint.x * cosRot - markerPoint.y * sinRot, markerPoint.y * cosRot + markerPoint.x * sinRot);
//Index of the type in atlas column and row int typeX = fmod(type, _MarkerSettings.x); int typeY = floor(_MarkerSettings.y - (type + 0.01) / _MarkerSettings.x);
float2 atlasMarkerUV = singleMarkerUV + float2(0.5, 0.5) + float2(typeX, typeY);
//make atlas UV to be within 0-1 atlasMarkerUV.x = atlasMarkerUV.x / _MarkerSettings.x; atlasMarkerUV.y = atlasMarkerUV.y / _MarkerSettings.y; marker = tex2D (_MarkersGraphic, atlasMarkerUV); //border colors marker.r = data3.r; marker.g = data3.g; marker.b = data3.b; //border colors c.rgb = c.rgb * (1 - marker.a) + marker.rgb * (marker.a); }
|
|
mike
New Member
Posts: 4
|
Post by mike on Apr 22, 2016 5:56:41 GMT
I know this post is old, but I'm wondering if there is an efficient way to find the border Hexes of set of Hexes, and then which marker and its rotation should be placed on it.
For instance, if I use HexNeighbors.GetRange(n,1) and get a List of Hex coords, then add 20 or 30 hexes extending from the borders of that shape, the resulting collection of hexes would have a kind of amorphous (non hexagonal) shape that defines my territory.
The best I can think of to get the borders (and haven't tried yet) is something like this :
For each hex in my 'territory' List. Get a list of neighbor hexes using GetRange(n,1) For each neighbor Check if that neighbor exists in the territory List If ANY DON'T, then this is a border hex. If ALL DO, then this is not a border.
This already seems like a bad approach ( O(n^2) ) and it still wouldn't tell me what border marker to show and what rotation to use. I know I could write out a ton of specific logic checking for all cases, but I've seen enough very elegant solutions to problems similar this, that I'm hoping there is already one out there. I was thinking something like - Using the above approach at first, moving from a random point in the territory in a straight line until you find your first Border Hex, then "walking" along it, only running the above code for hexes that are themselves border hexes until you end up back at the first border hex. Still don't know if thats the best way to do it (or if it will actually work) and still need to add a whole lot of conditions to find the rotation of the border marker.
Any advice would be appreciated!
|
|
mike
New Member
Posts: 4
|
Post by mike on Apr 27, 2016 7:32:04 GMT
Never mind, I went over the roads class and rewrote it to make borders. My approach seems really inefficient, but here it is if anyone needs a working starting point for the logic. I made changes to the HexMarkers method signatures, so this wont work right out of the box for you, but you can figure out what needs to change if you read through it all. I tried to add lots of comments. Also, set a new marker type "Border=40" which corresponds to where I put the border markers in hexselector.psd: using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using Pathfinding;
namespace HoneyFramework {
public static class Borders {
// Arrays used to determine which marker to use for a given border. // Bools represent if the adjacent hex is a border. [Down, clounter-clockwise to down-right] static List<bool[]> borderLayouts = new List<bool[]> { new bool[] {true, false, false, false, false, false}, new bool[] {true, true, false, false, false, false}, new bool[] {true, true, true, false, false, false}, new bool[] {true, true, true, true, false, false}, new bool[] {true, true, true, true, true, false}, new bool[] {true, true, true, true, true, true}, new bool[] {true, false, true, true, true, false}, new bool[] {true, false, false, true, true, false}, new bool[] {true, false, true, true, false, false}, new bool[] {true, false, false, true, false, false}, new bool[] {true, false, true, false, true, false}, new bool[] {true, true, false, true, true, false}, new bool[] {true, false, true, false, false, false} };
static List<bool> noBorderLayout = new List<bool>(){false, false, false, false, false, false};
static Vector3i[] directions = new Vector3i[6] { new Vector3i(0, -1, 1), //down new Vector3i(-1, 0, 1), //down - left new Vector3i(-1, 1, 0), //top - left new Vector3i(0, 1, -1), //top new Vector3i(1, 0, -1), //top - right new Vector3i(1, -1, 0) //down - right };
static public void RemoveBorders(List<Hex> territory) { // Remove border markers from the given hexes. foreach(Hex hex in territory) { HexMarkers.SetMarkerType(HexMarkers.MarkerType.None, HexMarkers.Layer.Borders, hex.position, 0f); } }
// Calculates and draws borders for the given list of hexes. static public void UpdateBorders( List<Hex> newTerritory) { foreach(Hex hex in newTerritory) { UpdateBorderMarker(hex, newTerritory); } }
// Brute force method to find and apply the proper border texture to the given hex. static public void UpdateBorderMarker(Hex hex, List<Hex> territory) { List<bool> layout = new List<bool>(); // Rotate around hex building an array of border/non-border bools. for(int loopIndex = 0; loopIndex < directions.Length; loopIndex++) { Hex neighborHex = World.instance.Vector3iToHex(hex.position + directions[loopIndex]); // Check if this neighbors direction is a border. bool isBorder = !territory.Contains(neighborHex); layout.Add(isBorder); } // If this hex has no borders... if (layout.SequenceEqual(noBorderLayout)) { // Remove any existing border marker on this tile. HexMarkers.SetMarkerType(HexMarkers.MarkerType.None, HexMarkers.Layer.Borders, hex.position, 0f); return; } // Find this layout in the borderLayouts array, along with rotation offset. int offset = 0; int bestLayoutIndex = -1; // For each borderLayout... for (int layoutIndex = 0; layoutIndex < borderLayouts.Count; layoutIndex++) { List<bool> currentlayout = new List<bool>((bool[])borderLayouts[layoutIndex].Clone()); // Check if layout pattern matches the current borderLayout pattern, in every possible rotation. for(int direction = 0; direction < directions.Length; direction++) { // Compare this orientaiton of the layout to the borderLayout we are looking at. if (layout.SequenceEqual(currentlayout)) { offset = direction; bestLayoutIndex = layoutIndex; break; } // Pattern doesn't match with this rotation. // Rotate the layout counter-clockwise one unit. currentlayout.Insert(0, currentlayout[currentlayout.Count-1]); currentlayout.RemoveAt(currentlayout.Count-1); } // If we found a match, stop searching. if (bestLayoutIndex != -1) { break; } } // Make sure we found a match. if ((bestLayoutIndex != -1)) { HexMarkers.SetMarkerType(HexMarkers.MarkerType.Border, bestLayoutIndex, HexMarkers.Layer.Borders, hex.position, HexMarkers.directionZeroOneScale[offset]); } else { Debug.LogError("Could not find border pattern for hex " + hex.position); } return; } }
}
Still need to work on the shader to draw on water, and not use ambient light, etc. but it's drawing the correct border markers. Attachments:
|
|