Posted on

Hadi: Nodes, Shaders and Blender

Hey Guys,

So I’ve been up to something pretty fun. Since we are in psudo 3D anyways I thought it would be fun to play around with blender to see if I can render out 3D objects into pixel art. The result should look something like this;

or currently in my case;

T here are a lot of resources and tutorials up to achieve this effect which makes me pretty happy. Once I get the “shape” of the object correct in belnder then I can move my image to photoshop for the finishing touches. I think this will give me more bang for my buck, but there are definitely quite a few preparations I need to make/learn.

Anywho back to work,

-Hadi

Posted on

Christian: Merging is always a headache

This week was a little more low key, thanks to me being busy with other things during the week. However, There was still some good progress this week. I got building transitions into a decent state. Walking through a building door trigger will cause the specified layer to fade to transparent if the layer is solid, and fade to solid if the layer is clear. Also new this week are auto jumps when the player walks off of the edge. This took a surprising amount of work to get done, but I’m quite pleased with the results. Of course, the week was not without challenges. I spent several hours trying to merge the fighting into the main branch, to no success. Hopefully it will go smoother the next time I attempt it.

Posted on

Dre: Back to Basics

So I’ve been doing a lot of drawing lately. Some are cool, most are rough. There is a beauty in being able to make elegant lines and subtle shading. I’m not particularly good at either.
This week I went back to what I know: painting. I forgot that I am good at art because when I’m not doing design, I’m letting photoshop handle the heavy lifting. So instead of trying to use tricks or try it things I’m not as confident with, I just decided to whip out the old stylus and a fat photoshop brush and play with color. It worked! I made art! It’s still not particularly sophisticated, but it’s a good step in that direction. It’s something that I feel like I can show the world without cringing (too much, anyways). The other thing is that the results didn’t take all that much time and have a much more solid result. So instead of experimenting or trying too hard, let’s make some art. My hope has been renewed and I’m looking forward to this week at [NAMESPACE].

-Dremundo
The Renewed Artist

Posted on

Sean: Buggsss

This week was a combination fixing a BUNCH of bugs that sprung up after separating out code for reasons of creating a better set of functioning AI classes. Horray… After getting my code back in line I spent the rest of the week creating four separate enemy types. So far I have a slow “walker” slime, swordsman “shadow-link” style, an archer and a heavy, none of their logic is all that complex but they all have unique behaviors and are fun to engage with. To simplify the UI of dealing with multiple enemies, I also added small health bars that hover above enemies and only appear when the player damages the enemy. With all the basics finally in place, we can merge the two halves of the project and switch from exploration to fighting mode! Needless to say, I am very excited to finally be able to run through an entire loop of our game.

 

Until next week,

Sean

Posted on

Isla: Link Roundup 4/26/16

For submissions, suggestions, or things to add, send ’em my way: @isla_es.

Video of the Week: Embracing Limitations” by Heavy Eyed (@heavyxeyed)

Song of the week: “That’s The Way It Goes” by Emily Fitch

Reading material: “Unconventional Tips for Improving your Programming Skills” by Livio De La Cruz (@LivioDeLaCruz)

Excerpt: “There are plenty of good resources out there that teach the technical skills that are necessary for becoming a good programmer, but I’ve seen only a few that give you the more personal lessons that you often only learn through experience or trial and error. In this article, I’m going to share with you some of my own habits and skills that I’ve gained over the years to help me do better work.”

 

Reddit thread of the week: “Currently most successful VR titles are Sitting Simulators: they rely on a mechanic/gimmick to keep the player stationary, to avoid motion sickness. How long can this keep up?” by /u/pshendry

Excerpt: “There are some super cool titles coming out, but it seems like the game mechanics are entirely based upon working around the limitations of VR. How many VR games can we play where you sit still in a virtual world before it gets tiresome? How many games can truly make use of Budget Cuts’ teleportation mechanic?

Random Shout-Out: Rebeki.b‘s character designs (@rebekieb)

 

Random Link of the Week: Click it if you dare

Posted on

Ken: He’s Not Dead…He’s Meditating

Have you heard that old tale about buddhist monks who can slow their heartbeat down to around eight beats per minute? It’s pretty crazy when you think about it, because a normal resting adult heart rate is somewhere between 50 and 100 beats per minute. Even while sleeping your heart rate only drops by about 8 percent to somewhere around 46 BPM. So, at 8 beats per minute, the blood of these monk’s seems to be flowing like the sap of a molasses tree. However, 8 BPM is actually fairly brisk compared to what one Tibetan monk was able to pull off: one beat per year.

In January of 2015, in the Songinokhairkhan district of the Mongolian capital Ulaanbaatar, a group of archaeologists and scientists exhumed the body of a monk that was over 200-years-old. Now here’s the crazy part, an expert in the ancient tradition of Buddhist lamas claims that this man isn’t dead, but rather in a meditative state known as “tukdam.”

This mummy – if you will – is said to belong to a teacher of Tibetan Buddhism, named Dashi-Dorzho Itigilov, who was born in 1852. Itigilov reportedly reached tukdam in 1927 while meditating and hasn’t moved since. Buddhist monks examined his body in 1955 and again in 1973, but the body remained in the lotus position and didn’t show any signs of decay. Even 75 years into his “meditation,” Itigilov is remarkably well-preserved.

Now, the skeptic in me wants to say that this old man mearly died peacefully while meditating – that some combination of luck and unique environmental conditions helped preserve the body so well. But what if this monk actually did achieve a rare form of enlightenment? What universal secrets might have opened up before him? What if Itigilov managed to achieve one of the greatest feats in the history of mankind and has actually become immortal?

And what will happen if, one day, he suddenly gets up and starts to move again?

Posted on

Hadi: Back to Work

So……… remeber how I thought I was done with construction last week?

Well I was wrong!

After getting excited about being able to get back on track with my animation and pixel art, I was once again roped into doing some manual labor. I guess on the bright side all that excercise is great for the body.

Anywho, I had this interesting idea that I’ve started.

I’m gonna take Andrew’s character concepts and start playing around with some animated portraits.

icon

(Rough start to character animation)

Since most of the detailed characters are in the sidescroller view, we won’t have too many super detailed characters. I thought that it’s kind of a waste of sweet character design to not at least try and incorporate what our NPCs really look like. . I think it would be pretty interesting to see how we can use these portraits to illustrate emotion, something we planned on doing anyway (Well with emojicons rather than portraits). It’s just a little experiment I’ve been playing around with and I think it would be cool. 😀

Other than that, I’ve been trying to figure out how to “tile” a ground on a tree. Now a normal tilset is pretty easy , here are some examples;

grasstile1

grassroad_tile_a1

grassroad_tile_a2

grassroad_tile_a4

grassroad_tile_d1

the trick part comes in when you try to put together asymetrical shapes together and create an organic “tileset”.

suburbs6

I think I’m close to figuring out an easy/good looking way of accomplishing this. Still a work in progress.

That’s all folks!

Now lets go work,

-Hadi

Posted on

Dre: The New Discoveries

So this week has be very tumultuous. Had a bunch of big things at my day job and went to SLC to visit my lady. Ladies are trouble, just so you know.
But as far as design goes, after talking to the crew, I’ve taken a step back from details and gone more towards concept. I have thought about how to rediscover the world and make everything mesh together better. The houses in the bakery district should be pastry related. This world is random, so anything goes! So I thought about how to make the buildings fit in to that mold and came up with some fun designs.

Also, I was thinking more in depth about how the people who live in this place go about their daily life. What is the environment like? What fuels their economy? What if things were less practical? How could the designs I’ve already come up with become more intimate and complex without having to draw all the complex details?

So the answers are that they live high in the trees. The wind blows heavily at that high elevation and rather than living on single large tree roots, they live in a jumbled mess of roots and branches. The trees have trees growing on them. Since the tree grows in the rocky highlands, who says it has to be a big solid tree? What if the tree was warped and twisted with age and weather? Perhaps the world has shifted and mountains have grown underneath this ancient tree. Perhaps the tree’s growth has shifted the planet it resides on. So I’ve been playing with a more interesting landscape under the tree. The people who live with it have only existed for a blink compared to the age of the tree. Anyways, things are progressing, complexing and solidifying more and more. It’s a crazy exploration, but I love trees and mountains.

-Dre
That guy you haven’t met yet

Posted on

Christian: Getting In The Zone

Zone transitions is one of the three big things that the overworld levels have to be able to accommodate (the other two being level streaming and layers) so it was about time to get it done. The good news is that it was relatively easy to abstract a zone as a container of levels, and then store that as a zone scriptable object. The bad news is that zones broke the navigation meshes. Welllll not really broke… more like, revealed some issues with the navigation meshes that were lurking beneath the surface. In that respect I’m quite glad to discover the problem quickly, but it was still a sticky problem to contend with. A large portion of the effort of getting zones working was directed towards fixing these problems. The problems stemmed from the Navigation grids that the AI uses. Namely, when a level was destroyed, the Navigation grid associated with it did not automatically disappear. Manually removing them on a level unload fixed one problem but introduced a memory leak. Because of how the A* pathfinding project manages its stored graphs, when one graph was deleted, the spot that that graph occupied in the graph array would change to null instead of being deleted entirely. After some extra effort, I managed to get all those leaks plugged, and we now have an solid zone transition system.

Posted on

Tutorial: Multi-Grid Graph Navigation in Unity

The A* pathfinding project is quite powerful, and allows multiple grid graphs out of the box, which is quite handy considering the requirements of our game. However, I didn’t find that many helpful resources for using grid graphs in the way we intended. What we wanted to do was move our AI from graph to graph as needed, without the graphs having to overlap. Essentially, we wanted our AI to go to the destination, even if the AI and the destination are one two different graphs. We ended up with a method to do just that, which I’ve documented here.

First off, our goal is to get an AI to move from its start position to a target position, even if the two positions are on different graphs. Lets start by doing a bit of housekeeping. Make sure to import the A* pathfinding project package into your project, and make a new scene.

Setting the Scene

screen_shot_2016-04-09_at_8.35.18_pm

As you can see, I’ve divided the level into four quadrants, with quadrant one (grey) positioned at (5,0,5) so that it’s bottom left corner is at the origin. This will be important later. In addition I’ve thrown some boxes around to give the AI obstacles to deal with. I’ve also made an empty game object and parented all of the level objects to it. This is just to make the scene view easier to navigate, and is entirely optional.

Making a Seeker

screen_shot_2016-04-09_at_8.37.28_pm

Next up is to make an AI to move around. Start by making a capsule object in the scene, naming it ‘Seeker’, and adding the Pathfinding > Seeker component to it from the add component menu. Also, go ahead and make an AstarAI.cs file in the assets inspector. Because we’re focusing on how to move from grid to grid instead of the intricacies of pathfinding, I’m going to use the “getting started” movement code instead of anything fancy. code is from here: How to setup the A* Pathfinding Project in a 2D Unity environment

For now, we’ll just copy paste this code into our AstarAI.cs file, and then add the script to our seeker object. After adding the script, create an empty game object called “target” and assign it to the AstarAI’s target field.

Generating Paths

screen_shot_2016-04-09_at_8.40.19_pm

Now that we have our seeker setup, lets make some paths for it to follow! If we tried to run the program now, we’ll get the error “There are no graphs in the scene.” To give the seeker something to follow, we have to first make an empty game object, and then add an Astar Path script to it. This script gives us a bunch of setting for generating grid graphs, and you should see one in the scene already, centered on our empty object.

Set Up the First Grid Graph

screen_shot_2016-04-09_at_8.42.06_pm

Lets get the Graph all sorted out. On the Astar Path script, click “Add New Graph” and then “Grid Graph,” then click the name of the resulting graph to change it from grid graph to Graph1. Leave the width, depth, node size and aspect ratio all at their defaults, and we should have a graph capable of perfectly covering one of our quadrants. So the first step is make sure the center of the graph lines up with the center of the plane that makes up quadrant one. In this case, we set the center to (5, 1, 5), so that our AstarAI script will keep the capsule up out of the ground. The only other thing we modify are the collision settings, which we modify by unchecking the Height testing bool and setting the mask for collision testing to everything.

Ogres are like Onions (Using Layers to Get Scanning Right)

screen_shot_2016-04-09_at_8.42.54_pm

If you click scan at the bottom of the Astar Path script, the script will generate a new grid graph for you, indicating the unavailable nodes with red cubes, and showing the connections of available nodes. Right away you’ll notice that the seeker capsule has caused surrounding nodes to be marked as impassible. To fix this, create a new layer called “AI” and assign the seeker capsule to it. Then, go back and uncheck the AI layer from the grid graph’s collision mask dropdown. Now after scanning the Seeker capsule shouldn’t affect the graph!

A Quick Test

tutorialmove1

If we’ve done everything right, putting the target on the same grid graph as the AI will cause the Seeker capsule to move to the target when we run the program. (Notice that I’ve changed the AstarAI’s next waypoint distance to 0.5 to make it get closer to the target before calling path complete).

Adding the Other Graphs

screen_shot_2016-04-09_at_8.47.50_pm

Now that we have that working, lets set up the other graphs! These graphs are set up in the same way as the first one, with only the center point changing. Here, you can see which graphs I’ve assigned to which quadrants. It’s crucially important to remember which graphs go with which quadrants, although you do not necessarily have to mimic my set up. If you do a different setup, make sure you keep track of what graph goes to what quadrant.

Trying to Move Between Graphs

screen_shot_2016-04-09_at_9.20.36_pm

As you can see, we never get our seeker moving because the path that we ask for comes back with an error. Right now we’re limited to only having the target on the same grid as our seeker.

Checkpoint

screen_shot_2016-04-09_at_9.21.45_pm

So this is all well and good, but we’ve basically just made the quick start tutorial with portions of the level that we can’t access. Weren’t we supposed to get the AI to move from grid to grid? To figure out how to get the AI to transfer grids, lets do some experiments with graph1 only. Specifically, I want to know what happens when our target or our Seeker aren’t on the grid.

Experiment 1 – Target off the grid

tutorialtargetoffgrid

When we ask the seeker to go to a destination that is off the grid graph, it appears to find the closest node it can, and go to there, then stop.

Experiment 2 – Seeker Off the Grid

tutorialseekeroffgrid

When the seeker itself is off of the grid, we can see that it moves to the nearest node to it, and then continues on to the target.

Method to the Madness

screen_shot_2016-04-09_at_9.32.37_pm

The purpose of these experiments was to show how we’re going to get the transfer from grid to grid to work. Basically, we want the seeker to move as far as possible on the grid that it starts in. when it gets to the edge of it’s graph, we want to create a new path using the grid that our seeker is next to. Because the seeker will move to the closest possible node on its graph even if the target is off grid, (or on a different grid) we don’t need to do any special waypoint calculations. Likewise, because the Seeker will move to a target even if it is off of the grid, we don’t need any special waypoint calculations for the new path either. All we have to figure out is which grid graph we should be looking at. It turns out that there is another implementation of the seeker.startPath command that takes a fourth argument. This argument is a bitmask that tells the pathfinder which graph(s) to check. Perfect!

A Bit More About Bit Masks

As we saw earlier, the grid graphs in A* pathfinding project aren’t smart enough to connect the dots themselves, so we have to tell the pathfinder exactly which graph we want to look at. The way we do this is through bit masks. There are many tutorials on bit masks, so I’m just going to give a brief explanation using only the parts that we need. Bit masks are a series of bits used to select, in this case, different graphs. Imagine a series of switches, where each switch is either 0 (off) or 1 (on). Each switch corresponds to a specific graph. So, because we have 4 graphs, our bit mask looks like this: 0000 where the rightmost 0 is the switch for graph 1, second from the right is graph 2, and so on. If I want to say “only look at graphs 1 and 3, and ignore the rest, then the bit mask I would create is: 0101. However, there isn’t a bit mask type with unity. Instead, you’ll notice that the bit mask argument in the startPath function is an int! This is because c# takes the integer and turns it into a string of binary when needed. So, instead of writing 0101, we would just input “5” into the parameter to get the same effect. Now, that’s a bit messy, so instead, we’ll use bitwise operations. The expression 1 << 2 takes the number 1 (binary 0001) and shifts it to the left by 2 spaces, resulting in a binary of 0100. in other words, 1 << 2 = 4.

The Graph Mask

The pathfinding project stores an array of all of the graphs in a scene. To tell the pathfinding project that we want the first graph in it’s array, we would use the operation 1 << 0 to get the bit mask for that graph. To find just the second graph we would use the operation 1 << 1, and so on. So, if we keep track of what order our graphs were created in, we can write a simple function to get the bit mask for a graph, given the bit offset for that graph.

public int GetGraphBitmask(int bitOffset) {return 1 << bitOffset; }

Finding the Graph Mask on the Fly

screen_shot_2016-04-09_at_9.33.35_pm

So, that’s a nifty way to get the bit mask that we need, but what about the graph offset? Well, we need to store it somewhere, so we can feed it to the function later. Also, we need a way of telling which graph our seeker is currently on. The easiest way to do that would be to store the bit offset for a graph in a dictionary, with the position of that graph as the key. However, we also need a way to relate the seeker’s position and the graph that they’re in. Rather than looking through the entire array of graphs, getting the center of each one, and finding which one is the closest to the seeker (although that would work, it would be slow) lets instead abstract the game world into a top down grid. For this to work we have several requirements. Each grid graph has to be the same size, and square, so that we end up with a perfectly square grid where each cell is the size of a grid graph. These are required because we need to be able to easily convert an object’s position in world coordinates into a cell coordinate. If we can easily convert an objects position into cell coordinates, then we can use the cell co-ordinates as the keys for our bitOffset dictionary, and thus easily pick out which graph the seeker should be looking at at any given position. Let’s also require, for the sake of simplicity, that cells have their origin at the bottom left. For us what this means is an object is in the cell(0,0) if that object has an x and z from (0,0), all the way to an x and z of (10,10). If an object has an x and z of (11, 5), then that object is in cell(1,0) and so on. basically, we divide the world into 10 x 10 cells, along the x/z plain. To get an object’s position in cell coordinates, all we have to do is take their position, divide by 10, and then round down (or cast to an integer).

The Levelmanager

screen_shot_2016-04-09_at_9.51.54_pm

This is all a bit much to do with just the AstarAI, so we’re going to make a level manager script to hold the logic to find the cell coordinates of an object, as well as the dictionary containing the offsets for the graphs, or in other words, a dictionary that tells us which graph goes to which cell. Go ahead and create a LevelManager.cs script, and attach it to a new, empty game object in the scene. Then, open the script in monodevelop so we can get scripting!

The One and Only LevelManager

screen_shot_2016-04-09_at_9.57.01_pm

Because the level manager is a unique object, we really only want there to be one of it at any one time. To do this, we add the following lines:

public static LevelManager instance;

void Awake() {

	if (instance == null) {
		DontDestroyOnLoad(gameObject);
		instance = this;
		Debug.Log("Instance is " + instance);
	} else if (instance != this) {
		Destroy(gameObject);
	}
}

this code makes sure that there is only one levelManager at a time, and that we can access that instance from anywhere with LevelManager.instance.

screen_shot_2016-04-09_at_9.57.24_pm

Now is a good time to add some fields to store the level size and the dictionary of bitOffsets, so add the following lines above the Awake() function:

public float levelSize;
public Dictionary< CellCoord, int> graphMaskOffsets = new Dictionary<CellCoord, int>();

Finding Cell Coordinates

screen_shot_2016-04-09_at_9.57.45_pm

Now, you may notice that CellCoord doesn’t exist. I found it helpful to make a new struct to hold the x and z coordinates of the cells, So let’s fix those errors by making the CellCoord data type. I’m going to make it right in the level manager file, just because I don’t feel like making an extra file just to contain a struct definition that’s so small. Type the following above the class definition of LevelManager:

[Serializable]
public struct CellCoord {
  public int x;
  public int z;

  public CellCoord(int x, int z) {
	this.x = x;
	this.z = z;
  }
}

The serializable attribute is so that this datatype will be editable in the unity editor.

Finding Cell Coordinates Cont.

screen_shot_2016-04-09_at_9.58.23_pm

Since we now have a Data type to represent cell coords, lets make a function to convert a Vector3 into a cell coordinate object. As mentioned before, this is simply a matter of dividing the relevant position coordinate by our cell size, and then casting to an int (which will cut off any pesky trailing decimals.) Add the following code below the Awake() function:

public static CellCoord GetCellCoord(Vector3 position) {
	int x = (int)(position.x / instance.levelSize);
	int z = (int)(position.z / instance.levelSize);

	return new CellCoord(x, z);
}

I’ve made the function static so that we can access it from anywhere (specifically the AstarAI script) using LevelManager.GetCellCoord(), and without having to store a reference to the level manager object.

Getting the bitOffset

screen_shot_2016-04-09_at_9.59.07_pm

The next thing we need to do is return a bitOffset to help us find a specific grid graph, when given a specific cell coordinate. We could access the dictionary with LevelManager.instance.graphMaskOffsets[cellCoord] but that’s a little unwieldy. Instead, lets make another static function to access the dictionary internally, so our AstarAI can get access smoothly.

public static int GetGraphMaskBitOffset(CellCoord coord) {
	return instance.graphMaskOffsets[coord];
}

Adding Offsets in the Editor

screen_shot_2016-04-09_at_10.00.02_pm

The final thing we have to do for the level manager is to make it possible to add bitOffsets to the dictionary from the editor. Note that dictionaries do not show up in the inspector, so instead we’ll make an array with all of the relevant information, and then transfer that information to the dictionary during the awake function. As before, I’m going to create a new data type, which is simply the combination of our CellCoord data type and an integer representing the graph’s index in the graph array (i.e. the bitOffset). Also as before, I’m just going to add it to the level manager file instead of making a new file just for it. Add the following code above the LevelManager class definition:

[Serializable]
public struct GridGraphBitOffset {
  public CellCoord cellCoord;
  public int graphBitOffset;
  public GridGraphBitOffset (CellCoord cellCoord, int bitOffset){
	this.cellCoord = cellCoord;
	this.graphBitOffset = bitOffset;
  }
}

screen_shot_2016-04-09_at_10.01.45_pm

Then add the following code above the Awake() function:

public GridGraphBitOffset[] gridGraphBitOffsets;

Finally, add this for loop to the last part of the Awake() function:

foreach (var bitOffset in gridGraphBitOffsets) {
	graphMaskOffsets.Add(bitOffset.cellCoord, bitOffset.graphBitOffset);
}

Adding the Graph Data in the Inspector

screen_shot_2016-04-09_at_10.12.56_pm

We can now open the levelManager script in the inspector, and add our cell definitions. For example, if graph2 is in the bottom right quadrant, then we can make a new element on the gridGraphBitOffsets array, set the cell coords to (1,0), and the bitOffset to 1. Do this for each graph, and all of our cells will be defined.

Setting the Graph from cellCoord

screen_shot_2016-04-09_at_10.07.07_pm

With the levelManager complete, we can focus on the AstarAI. Basically, besides moving from point A to point B, the only other job our AI will have is to handle the transition from one grid to another. To start with, if we give our AI a cell coord, we need to be able to get the graph associated with that cell. To do this, open the AstarAI script, and add the following code below the fixedUpdate function:

public int GetGraphMask(CellCoord cellCoord) {
	return 1 << LevelManager.GetGraphMaskBitOffset(cellCoord);
}

You’ll notice that this is the equation from the bit mask section. What this function does is return a bit mask that specifies the graph for the given cellCoord.

Setting the Graph from Vector3

screen_shot_2016-04-09_at_10.08.07_pm

It will also be useful to add an implementation of the GetGraphMask function that finds the mask directly from the given position. Add the following code below the previous function:

public int GetGraphMask(Vector3 position) {
	CellCoord cellCoord = LevelManager.GetCellCoord(position);
	return 1 << LevelManager.GetGraphMaskBitOffset(cellCoord);
}

Getting the Start Graph

screen_shot_2016-04-09_at_10.08.51_pm

Now that we have a way to get the graph mask, we can change the startPath() call in the Start() function by adding a call to GetGraphMask() as the final argument. after modification, the startPath call should look like this:

seeker.StartPath( transform.position, target.position, OnPathComplete, GetGraphMask(transform.position) );

Progress

tutorialpartway1

If we run the program now, the seeker should move towards the target no matter where that target is, but it will stop when it reaches the end of the graph that it starts on. Here is where we handle the transition.

Getting the Next Graph

screen_shot_2016-04-09_at_10.16.08_pm

We need a function to tell us which graph we should look at next, given our current position in cell coordinates, and the target position in world coordinates. The following function looks a little complicated, but the steps are relatively simple. To find out where we should go, we first take the target’s position in world coordinates, and subtract our current position in world coordinates to get a vector pointing from us to the target. Next, we check the magnitudes of the different components of the vector to see if the x or the z is larger (the y axis we don’t care about in this case). If the x axis is larger we add an offset to our current cell position in the direction of the sign in the x axis. e.g. if the x axis has a larger magnitude and was -3, then we add -1 to the x value of our current cell position. If the z value is higher, we do the same, but for the z value of our current cell position. We then return the value of our current cell position plus the offset to get the coordinates of the cell that our seeker AI should transfer to next. Add the following code below the previous functions:

public CellCoord GetNextCellCoord(Vector3 targetPosition) {
  Vector3 direction = targetPosition - transform.position;
  CellCoord currentPosition = LevelManager.GetCellCoord(transform.position);
  CellCoord offset = new CellCoord(0,0);
  if (Mathf.Abs(direction.x) > Mathf.Abs(direction.z)) {
	offset.x = (int)(1 * Mathf.Sign(direction.x));
  } else {
	offset.z = (int)(1 * Mathf.Sign(direction.z));
  }

  return new CellCoord(currentPosition.x + offset.x, currentPosition.z + offset.z);
}

Getting a New Path

screen_shot_2016-04-09_at_10.18.10_pm

Of course, this function is useless without calling it! To tell the AI to keep going, we’ll need to tell it to start another path, with the next graph selected after it gets to the edge of it’s current graph. To do this all we have to do is check the AI’s position against the distance to the next waypoint plus an offset (so it doesn’t get confused when we actually finish our path). If we’re still far away from our target and we’ve reached the end of our path, then we have to keep going! You’ll also have to add a private bool to check to see if we’re already trying to calculate a path. If your computer is slow in finding a path, we could accidentally trigger another search, which would override the path you’re already looking for, and our AI would go nowhere. Add the following code above the start function:

private bool calculatingNewPath = false;

Also, we’ll have to change the OnPathComplete function to use the new bool. Change the OnPathComplete function to look like the following:

public void OnPathComplete ( Path p )
{
  Debug.Log( "Yay, we got a path back. Did it have an error? " + p.error );
  if (!p.error)
  {
	path = p;
	//Reset the waypoint counter
	currentWaypoint = 0;
	calculatingNewPath = false; // <<<<<<<<<<< important to add!
  }
}

screen_shot_2016-04-09_at_10.19.00_pm

Finally, we can update the way we determine we have reached the end of the path, in order to accommodate multiple grids.

Add the following code to the fixedUpdate call, in the body of the current waypoint check. After modifying, the body of the current waypoint check should look like this:

if (currentWaypoint >= path.vectorPath.Count)
{

// Magic number is here so that if we get to the end of our waypoint list we won't try to keep going.
if (Vector3.Distance(transform.position, target.position) > nextWaypointDistance + 1 && !calculatingNewPath) {
  seeker.StartPath(transform.position, target.position, OnPathComplete, GetGraphMask(GetNextCellCoord(target.position)));
  calculatingNewPath = true;

  return;
}

  Debug.Log( "End Of Path Reached" );
  return;
}

Putting it All Together

tutorialcomplete

Now, if everything is working properly, we should be able to see our seeker move to our target position regardless of which graph it’s on when we run the scene!

tutorialfinalmove

You’ll notice that the seeker always moves to the closes node to the target that it is currently on before moving to the next graph. This can give some weird behavior specifically because we don’t have our GetNextCellCoord set up to do diagonal offsets. Just something to keep in mind!