|
Post by 7thsage on Jun 26, 2018 1:21:54 GMT
I've been playing with the pathfinding code a bit more ( previous post). I've now got jumping over gaps work, and the start of terrain cost. So I thought I'd add post those up as well. I'm not sure how much more I'll change, but I figured I'd consolidate what I have so far into its own thread instead of hijacking the other one. The changes here are a bit more breaking and I'm expecting to have to change a few things once I hit the later parts of the tutorial series, but I like the result well enough so far. The first change I made was to the Tile.cs file so I could keep track of which tiles are empty [HideInInspector] public bool isEmpty; The biggest changes are in Board.cs To the Search function I added a couple addDirs, which replaces the array of tiles to check next so I can check beyond the immediate four tiles, and travelcost, which I needed so I can set distance to more than one when the distance is larger. Later on I'll also use it to determine difficultly traveling over different tile types, but I need to actually have different tile types first, so that will have to wait. Then I changed the for loop to use count instead of hardcoding 4 and used the functions I added. public List<Tile> Search(Tile start, Func<Tile, Tile, decimal, bool> addTile, Func< Point[]> addDirs, Func<Tile, Tile, decimal> travelCost) { List<Tile> retValue = new List<Tile>(); dirs = addDirs(); retValue.Add(start);
ClearSearch(); Queue<Tile> checkNext = new Queue<Tile>(); Queue<Tile> checkNow = new Queue<Tile>();
start.distance = 0; checkNow.Enqueue(start);
while (checkNow.Count > 0) { Tile t = checkNow.Dequeue(); for (int i = 0; i < dirs.Length; ++i) { Tile next = GetTile(t.pos + dirs[i]); if (next == null) { GameObject instance = Instantiate(tilePrefab) as GameObject; instance.SetActive(false); next = instance.GetComponent<Tile>(); next.pos = t.pos + dirs[i]; next.isEmpty = true; next.distance = int.MaxValue; } decimal cost = travelCost(t, next); if (next.distance <= t.distance + cost) continue; if (addTile(t, next, cost)) { next.distance = t.distance + cost; next.prev = t; checkNext.Enqueue(next); retValue.Add(next); } } if (checkNow.Count == 0) SwapReference(ref checkNow, ref checkNext); } return retValue; }
In Movement.cs I added a lot to movement. The 4 directions to check here protected Board board;
protected virtual Point[] SurroundingTiles() { Point[] dirs = new Point[4] { new Point(0,1), new Point(0,-1), new Point(1,0), new Point(-1, 0) }; return dirs; } protected virtual decimal RangeFunction(Tile from, Tile to) { Point distVect = new Point(Mathf.Abs(from.pos.x - to.pos.x), Mathf.Abs(from.pos.y - to.pos.y)); decimal distance = distVect.x + distVect.y; if (distance > 1) return distance + 0.1m; return distance;
}
public virtual List<Tile> GetTilesInRange(Board b) { board = b; List<Tile> retValue = b.Search(unit.tile, ExpandSearch, SurroundingTiles, RangeFunction); Filter(retValue); return retValue; } protected virtual bool ExpandSearch(Tile from, Tile to, decimal cost) { if (to == null || to.isEmpty == true) return false; return ( from.distance + cost ) <= (decimal)range; } protected virtual void Filter(List<Tile> tiles) { for (int i = tiles.Count - 1; i >= 0; --i) { if (tiles[i] == null || tiles[i].isEmpty == true) { tiles.RemoveAt(i); } else if (tiles[i].content != null) tiles.RemoveAt(i); } }
WalkMovement.cs got changed up this time around protected override Point[] SurroundingTiles() { Point[] dirs = new Point[8] { new Point(0,1), new Point(0,-1), new Point(1,0), new Point(-1, 0), new Point(0,2), new Point(0,-2), new Point(2,0), new Point(-2, 0) }; return dirs; }
protected override bool ExpandSearch(Tile from, Tile to, decimal cost) { //skip if the distance in height between the two tiles is more than the unit can jump if (( Mathf.Abs(from.height - to.height) > jumpHeight )) return false; Point distVect = new Point(Mathf.Abs(from.pos.x - to.pos.x), Mathf.Abs(from.pos.y - to.pos.y)); decimal distance = distVect.x + distVect.y; if (distance > 1) { for (int i = Math.Min(from.pos.x, to.pos.x); i <= Math.Max(from.pos.x, to.pos.x); ++i) { for (int j = Math.Min(from.pos.y, to.pos.y); j <= Math.Max(from.pos.y, to.pos.y); ++j) { Point newPoint= new Point(i, j); if (newPoint == from.pos || newPoint == to.pos) continue; Tile test = board.GetTile(newPoint); if (test != null && test.height >= from.height) return false; } } }
//skip if the tile is occupied by an enemy if (to.content != null) return false;
return base.ExpandSearch(from, to, cost); }
And finally in FlyMovement.cs and TelportMovement.cs just tweaked these a little protected override bool ExpandSearch(Tile from, Tile to, decimal cost) { return ( from.distance + cost ) <= range; } In BattleState.cs I didn't change anything from before, but here's what I changed to make the selection indicator move over the blank spots to work with the new movement. protected virtual void SelectTile(Point p) { if (pos == p) return; Vector3 indicatorPosition; pos = p; if (!board.tiles.ContainsKey(p)) indicatorPosition = new Vector3(p.x, 0, p.y); else indicatorPosition = board.tiles[p].center; tileSelectionIndicator.localPosition = indicatorPosition; } And to make sure I don't get an error when trying to select over an empty tile In SelectUnitState.cs(at the beginning of the OnFire method) if (owner.currentTile == null) return;
As before would love to hear anyone's comments/suggestions, and can maybe help someone else trying to do something similar.
|
|
|
Post by hawaiiand on Jun 26, 2018 18:48:10 GMT
Im not the right one to give any actual usable feedback, but I just wanted to say that it looks fantastic and thank you for continuing to update this forum. I have already incorporated your changes from your first thread. I'm currently messing around with a Voronoi generated map and trying to tie its 'map nodes' with this tutorial's "Tile" object, so I'm holding off on consuming these changes for now, but it's right in line with what I'm imagining.
Thank you for providing your examples and code, and thank you for writing the post in such a way that explained it well and the reasoning behind it (much like the original author and tutorial).
Hope to see more from you as time goes on!
|
|
|
Post by 7thsage on Jun 26, 2018 20:52:10 GMT
Thanks. Glad you could use at least part of it. Hopefully I'll have more to share, as long as I can find some more parts to tweak that are a bit more general instead of things that would be just more specific to what I'm doing. Good luck with your tiles.
|
|
|
Post by 7thsage on Jun 26, 2018 21:54:53 GMT
Haha, just realized I had an issue with my code. None of the parts I changed in the post, but you can see it in the video. The walk character never walks, but hops everywhere. The problem was that when I was doing the tutorial, I copied the jump function twice. One for walk, and the second for jump.
|
|
|
Post by Admin on Jun 26, 2018 23:00:18 GMT
Glad to see you making progress! Overall looks pretty good and I can see you putting a lot of work into it. I haven't thoroughly reviewed your code, but here are a few thoughts / comments based on a quick look:
I'm not sure about the "SurroundingTiles" stuff. With your current implementation it looks like you check all tiles up to two spaces away in each of the cardinal directions. I assume this is to allow a unit to jump over single tile holes. Have you considered the scalability of this solution - such as what if you want a unit that can jump over larger holes? I wouldn't want to keep expanding the array. Another concern is if you are checking tiles two tiles away all the time - even if there isn't a hole. This would allow your units to "jump" over other obstacles that you would otherwise have wanted them to need to path find around.
Another option might be to do a dynamic list of tile locations, that looks in the cardinal directions given a current tile, and finds the next closest tile assuming there is one. You could just make a loop from the current tile position and capped by the extents of the board, but break early when the next tile is found. Then at most, you would only ever look at up to four tiles at a time, you could handle jumps over gaps of any size, and you wouldn't need to worry about bypassing obstacles. Hopefully that makes sense.
|
|
|
Post by 7thsage on Jun 27, 2018 0:08:52 GMT
Yea, scalability is an issue. 4 more tiles for every extra jump distance. The plan was to calculate them with basically a for loop multiplying the first four times the jump range. The problem with cutting it at the first result in each direction is the other use case for jumping over valid terrain, such as over tiles when jumping from roof to roof, or just when a tile in front of you dips, but the tile in front is still the same height, you'd be able to jump over the dip. I'm hoping it won't be too much of a problem, but I guess it depends on how many characters on the screen can jump and how far.
edit. to prevent jumping over obstacles, I've got another check that after it finds a location at a valid height to jump to, it checks the tiles inbetween if they are lower, or later on if they have an obstacle. One more thing to add to the complexity of the search:(
for (int i = Math.Min(from.pos.x, to.pos.x); i <= Math.Max(from.pos.x, to.pos.x); ++i) { for (int j = Math.Min(from.pos.y, to.pos.y); j <= Math.Max(from.pos.y, to.pos.y); ++j) { Point newPoint= new Point(i, j); if (newPoint == from.pos || newPoint == to.pos) continue; Tile test = board.GetTile(newPoint); if (test != null && test.height >= from.height) return false; } }
|
|
|
Post by Admin on Jun 27, 2018 2:03:35 GMT
Ok, as long as you've thought it all out I'm happy.
I had another idea though, that I figure I can throw out in case it is helpful. Currently all tiles are only "connected" based on their point position's adjacency to other tile point positions. You could modify this such that each tile is essentially a node in a graph, where each node knows which other nodes are considered neighbors. In this way you could allow the map to predetermine where "jumps" would be allowed because the tiles at each side of the gap would link to each other. The tile nodes would still have their position information which would be helpful to determine travel distance / cost, but I think it could be really helpful in reducing the complexity of a search algorithm for these kinds of scenarios.
As a side benefit, this approach also allows for easy implementation of additional game mechanics such as: * one-way passages * warp locations based on the board itself rather than the unit's movement * dynamic node connections, such as how opening a door might connect inside tiles to outside tiles, or destroying a wall could could provide a shortcut, etc.
|
|
|
Post by 7thsage on Jun 27, 2018 4:18:44 GMT
That would certainly reduce the excess computations. What if I were to set up a list on each tile for each jump range and if the jump is zero set it to just use the default four directions. then instead of passing the range function in, I could just pass in the characters range as an int? After that it would just be a matter of creating a function to loop through all the tiles at startup to precalculate them(and update the area around any tile that updates during gameplay)
|
|
|
Post by Admin on Jun 27, 2018 17:23:14 GMT
Im not sure I understand exactly what you are proposing, but since I treat this as a prototype I would do whatever you are most comfortable with - you can always incrementally improve on the idea over time and if you end up not using a game mechanic there is no reason to have a more complex architecture that supports it.
With that said, I would still favor the manual node connection approach because having a tile with a jump range doesn't necessarily indicate directionality, so if you extend your search range back in-land you are doing more work than needed. You could try using a vector so that you could have both jump range and directionality paired though... there probably is a solution you could make work.
|
|
|
Post by 7thsage on Jul 1, 2018 9:19:40 GMT
found another small issue with what I had posted. Wasn't going to jump animation if the terrain from and to were the same height. quick change. Gonna hold off on any bigger changes here until I figure out what the terrain is going to look like.
In WalkMovement.cs
int distance = Mathf.Abs(from.pos.x - to.pos.x) + Mathf.Abs(from.pos.y - to.pos.y); if (from.height == to.height && distance <= 1)
|
|