As part of our first project for Studio 2, we have to create a creature based AI. Conditions for this being:
- Must be able to pathfind within the world. The pathfinding algorithms must be implemented individually. Built in pathfinding or third party solutions are not permitted to be used.
- Have individual behaviours where the individual behaviours attempt to achieve some goal (eg. collecting food). Have a higher level AI that decides which behaviours to run.
My Creature Feature:
Instead of a creature, I’ve decided I’m going to use Taxi’s in a procedurally generated city fighting over fares. Each taxi will have their own set of Trust logic and Behaviour logic systems with which they will be able to decided on whether they should help their fellow AI or not.
Goals:
Just kidding,
My goals for this project are:
- Node based Movement and generation
- Procedurally generated cities using Perlin Noise
- A* pathfinding logic
- Navigation system
- Behaviour Logic
- Trust Logic
- Traffic system (depending on remaining time)
Progress:
So far progress as been swift, i attribute most of this to the amount of planning that i did in advance.
Node based Movement and generation:
As my project is based in a city, its was easy to generate a grid. Each of the nodes hold their own data for their World Position, GameObject and if they are traversable or not
A* pathfinding logic:
This was my first attempt at A*, its not perfect and it is still in sort of a base state as i will need to modify it for upcoming changes. However its functional.
Further refinement and optimising will be done at a later stage.
Procedurally generated cities using Perlin Noise:
The above GIF shows how i am currently working out connecting a road system for my city. It does this in a few steps:
Current Generation:
1. Create all nodes and apply Perlin Noise to them
2. Perlin Noise over a certain amount then register as Seed Points for road generation (Green)
3. Then it picks a Seed Point close to the centre and pathfinds to every other Seed point.
From here it then creates road tiles on that those nodes creating a road system and will fill the rest in as city tiles.
However this will end up with some strange looking cities to improve upon this i will be changing it to:
New Generation:
1. Create all nodes and apply Perlin Noise to them
2. Perlin Noise over a certain amount then register as Seed Points for road generation (Green) Also four Seed Points will be generated in the centre of the map
3. Quadrants will then be made corresponding with each centre Seed Point
4. Each centre Seed Point will then path find to everything in its quadrant
5. Seed Points will then path find to each other
This should improve the overall layout of the city and make it seem like it is more structured.
Progress Update:
As we come to a close of Week 3, its time for an update of all the things that have been changed.
Finished Generation:
The procedural generation has gone through many iterations but i’m starting to get to a stage where i’m pretty consistently happy the results.
It takes a few passes to create this process, which happens as follows:
1. Start With Panel | ![]() |
2. Create Grid | ![]() |
3. Apply Perlin Noise to nodes to assign Seed nodes | ![]() |
3. Create Centre Seed nodes, these will have a random chance of being cityTiles or road | ![]() |
4. Put Seed nodes into quadrants | ![]() |
5. Path find from Centre seed nodes to those in it’s section | ![]() |
6. Block centre to prevent roads cluttering | ![]() |
7. Connect Seed Points clockwise | ![]() |
8. Unblock centre | ![]() |
9. Fill gaps with city tiles | ![]() |
10. Create Features, ie. border is water and unaccessible tiles are grass | ![]() |
11. Roads accurately laid out to look more realistic (i’m not gonna draw all that i will be here forever) | ![]() |
12. Pick a random cityTile and make it the depot | ![]() |
13. Add Taxis to random locations, Taxis number will be a variable | ![]() |
This processing turns out looking pretty good 90% of the time, like below:
However in saying that there is one error in the logic where the map is sometimes split.
Its not every time though and i figure i can come back to this later as it wont be too much work to fix. I’m almost certain its a problem with not connecting every node, this might be to do with the centre spacing i created previously.
This is what the Error looks like when generated.
Notice the top section cut off from the rest of the map.
Start of Navigation:
Navigation has been an interesting roller coaster for me over the last couple of days.
I went in without a plan and tried to rewrite the pathfinding to accommodate for the integration with the navigation system. The idea being that the units (Taxis) sent requests through the one navigation system that worked with the pathfinding.
After a few hours and much heartache, i decide to scrap it. Instead forming a re approach with my teacher the next day, changing the navigation to have its own separate systems per unit.
Fare System:
With the map pretty much sorted i started on generating the fares for the taxis. Currently the system is very primitive, spawning them randomly on cityTiles. However i have it in scope to generate different size buildings based on cityTiles densities that can produce more fares more often.
Once again though i feel like this can be done later, whats important right now is focusing on the behaviour logic and making sure that i end up with a functional project.
Progress Update:
As we come to a close of Week 4, its time for an update of all the things that have been changed. I think i’m going to change the format though and talk about my goals i set last week (Find in my other blog post Reflections) and where i am in regards to those now. Here were my goals for the end of this week:
Goals for Week 4:
- Have My navigation fully functional
- Completed most of my behaviour and trust logic
Navigation:
As mentioned previously, i first started navigation by altering my pathfinding, this ended up being scraped as it was messing with my preexisting work. This week i made sure it was its own separate entity that just requested the data for the pathfinding system.
Not only did this save me a lot of grief with integration but it is essential for my multi AI accessing this information.
So whats so special about the navigation? why not just take in the pathdata?
As a developer we are always looking at ways at improving the Players experience, and this experienced can easily be ruined by janky AI. Mostly the navigation is to improve visual appearance however it also serves an important role in the “intelligence” of the AI system.
For Example:
Now i’m not saying this is perfect, but it sure is a lot better experience than just appearing tile to tile. Not to mention this can still be tweaked for better results however at this stage i’m pretty happy with its abilities. Both establishing if paths are usable and its general navigation of tiles.
Behaviour & Trust Logic:
This has been a real pain in my side, i was told that the hardest part of this project was implementing A* but i have to disagree i think its logic. Even though i planned this (to what i believed to be pretty well) there is always things that you miss, AI breaking things.
Major issues I’ve had so far,
- Navigation to CityTiles
- Refuelling
- Checking information
Now i’m going to be honest, some of these took longer than expected due to silly syntax errors. However of those three major issues, only one remains, the checking of information. See AI freak out here:
The block above the AI’s head represents the state it is currently in, this flashing is caused by a conflict in behaviours, i should have this sorted out in the next couple of days though. So overall i’m pretty close to “completion”.
Why did i mark completion like that?
Well considering i’m currently a head of the project, its time to challenge myself to new systems. This means that my current logic State Machine will be changed over the next week into a Goal Orientated Action Plan (GOAP) combined with a State Machine. Hopefully resulting in less obvious and more “Natural” results from the AI behaviours.
Non Goal Updates:
(i’m sure you have noticed these already)
- New Camera system
- Visualisation of State
New visualisation have been added in the form of the vehicle and a mini map, the intent being that you will be able to select and cycle through the multiple taxi cameras.
Talking of intent (great segway), the intent of the AI is now visible in form of a coloured block above its head. This helps in both debugging and visual intent for the actual project. The colours and states associated are as follows:
CYAN | SEARCHING LOCAL AREA |
BLUE | SEARCHING INFO |
YELLOW | CHECKING LOCATION |
GREEN | TRANSPORTING PASSENGER |
RED | HEADING TO DEPOT (REFUELLING) |
Progress Update:
As we come to a close of Week 5, its time for an update of all the things that have been changed. Here were my goals for the end of this week:
Goals for Week 5:
- Fully functioning logic
- Functioning Multiple AI
- Have Goap fully integrated
Logic:
So i spent a far amount of hours testing and tracking down all logic issues, which lead to some slight improvements, the major improvements was once i changed the fare system.
There was a couple of issues in the fare system:
1. Multiple fares at locations:
Previously i went with a quick implementation of fares and i was randomly spawning fares at any city location, this results in a few things:
- Taxis interacting with multiple fare data
- No control of fare spawns*
- No tracking of fares*
2. How the taxis checked this data:
Another limiting factor was the information the fares gave the taxis, a lot of the logic issues were caused by the taxis checking a known fare locations (or so i wanted it to be) but the only information they had was the destination of the fare.
How were these solved?
1. I added additional power to the previously made spawn manager (which was really not managing that much before hand), now it recorded the spawn locations of all fares in a list and was updated by the fares being collected. This solved the issue of multiple on one tile and now i was able to track fare positions for later use for my Save/Load data.
Another byproduct of knowing spawn locations was making the spawn manager prioritise skyscrapers* for spawning. (Will talk about that later)
2. This was pretty simple, i just added a extra variable to the people location class which held the fares transform position plus its height.
Multiple AI:
Previously i was having issues with multiple AI running, by that i mean only one taxi would actually move, i figured this was because both taxis were trying to call a function on the same frame to get a path and it was conflicting.
So to solve this a created a index when the taxi spawned which meant functions of that taxis would only be called on that certain frame. However this still didn’t solve the issue so i went searching.. in the end it was a simple mistake of having a find object of type for the navigation component, so all the taxis were grabbing the same navigation script.
Now i have functional multi AI, shown by the moving cameras below
Goap:
Now this is a area i have been struggling with, unfortunately i didn’t complete this goal this week. I will go over this more in my reflection diaries but will sum it up here. The systems i have been looking at have been far more complicated than I’ve needed for the project and I’ve been trying to use and replicate those systems.
Considering this is pretty much the final step in the project (apart from mass testing) its not so bad that i have been struggling with this (as i still have time to make it happen)
Super Bonus Extra Round:
Don’t know why i wrote that, i’m going to say its just lack of sleep today. This is the other stuff i did this week that wasn’t a goal. Theses extras are:
- Water Update:
So previously the water on my map was limited to the outside tiles, which left a lot to be desired with different aesthetics. I added a function that took that further however the order i went about it meant that it missed tiles on the underside of the island. This gave some strange results, like so:So i added a function that would flip this array data and then basically do the same thing, so it wouldn’t miss the underside of the island. Which looked much better, Results below:
- Skyscraper / city update:
This was the idea of creating skyscrapers in built up areas that had an increase rate of spawning fares, something my teacher suggested to my a while ago in regards to how the fare system should operate.
Skyscrapers are decided by checking the neighbour nodes around it and if three of the neighbours nodes are city tiles a skyscraper is born.
Through the new improved spawn manager system as well, i’m able to keep track of these and increase the fare spawn rate as intended.
It also gives a bit of variance to the city without doing to much which looks pretty great. - Json FileReader:
So this was supposed to be the final step in the project but i sort skipped ahead to it while i was stuck on goap. I had a few issues with trying to pass through an array into json, i eventually solved this by making a wrapper for the jsonUtility and passing it through that way. Overall i’m pretty happy with the success of this, i was able to have this full operationally within a few hours and it is able to retain all information from a build. You can find my file reader here
Progress Update:
As we come to a close of Week 6, its time for an update of all the things that have been changed. Here were my goals for the end of this week:
Goals for Week 6:
- Have Goap fully integrated
Goap:
I am so close to completion, the priority, goals and actions are pretty much completed and just need a few teaks before its ready for implementation (then the real fun begins).
Extra Updates:
These were entirely cosmetic updates
Sand/ terrain update:
This was mostly just created to hide a issue in my water logic, well i could have fixed this issue by making the entire surroundings into water but i wanted to have some inconsistencies, all i did was make grass tiles touching water become a sand tile and then the tile behind it. this gives some variation and not just a cut off between land and sea.
Mountain/terrain update:
My maps were looking quite baron, especially when large grass sections were featured between packed cities, so i though about things i could fill this void with and mountains made sense (especially being an island).
To create these first i checked to see if there was room to create a mountain tile (at least one spacing from a city).
Then i connected mountains by filling in the gaps between mountain tiles and used my old friend perlin noise to generate the height.
Lastly any solo mountain tiles are then deleted, leaving what looks like pretty uniformed mountain regions .
Day/Night cycle update:
So this started as i wanted to be able to define what a night was (this is because the taxis main goal is to make the most money in a night). However it really serves no other purpose than that, it just looks pretty.
Progress Update:
This should be my final Progress Update (i will update if i end up tweaking anything)
Goals for Week 7:
- Finish Goap
- Testing make sure everything in creature feature works
Goap:
So with all the goals and actions laid out it was a matter of linking together and knowing each state of the goal. This would be controlled by a “Ai Brain”
To help me visual how this progress should look i made a gif displaying the outcome i desired
It would reshuffle goals based on their urgency (priority in this case) and sleep any currently running goals that need revisiting.
You can find my code for the AI Brain Here .
So how did it end up looking?
Well its not as pretty as the gif above (alright that might be subjective) but it functions!
Testing:
While implementing the goap system didn’t take too long, it was the subsequent logic of those system that was the real pain.
You see previously all my logic was dealt in one state machine called behaviour logic, now however this was split into sections, which make its easier to track things right? Well right and wrong, sure its easier to suss out the issue but my logic itself wasn’t always concrete, which meant i ended up having to rework a lot of it.
Here is a list of the issues i had:
- Not cycling Movements:
This was solved by changing moving the actions to the update method. - Json save not spawning new tiles:
Just had to add the new tiles that i created previously. - Sometimes starts with no commands:
This was solved by changing moving the actions to the update method. - Doesn’t organise priority:
Was not properly using linq (my error). - Not transporting:
Transporting logic error. - Perform trip error:
This was solved by changing moving the actions to the update method.
- Storing client location issue:
Error was caused by previous send info to taxi logic - Consuming huge fuel near depot:
Error with navigation logic
Also i had to fully rework the depot and refuelling function, (my old logic was a mess) so instead the taxis themselves dealt with the depot instead of vice versa making it a smoother and more accurate process.
Other things:
Mountain Pitch Update:
In the mountain gen process i lowered all the mountain range to begin with and then increased the height of the middle tiles to add a more pitched (“realistic”) looking mountain.
Mountain Update:
I gave random variances that removed mountain tiles and then activate a follow on effect, ie. if this one was destroyed give a random chance for its neighbour also to be destroyed. This created alcoves and different mountain structures rather than just being a solid block.
Taxi Cameras, Taxi UI:
The other taxis always had cameras, now i created the functionality to cycle between them all, i also added a UI which display there taxi number id on their indicator (Its not shown here because i have it on a different resolution, add that to the list of things i need to change)
Live Taxi Stats:
I needed a way to display the information of each taxi for when the project was built, also it was in my best interest to track information this way in the developer mode as well.
I created a system that
- Creates a table of UI text boxes, adds each text box to a list
- Creates a set of each taxi info (scale-able)
- Creates a list of all that information except the table
- Pass information from that list of all contents to appropriate text boxes (constantly updates)
That’s it for now!
So i will update this, with a link to a post mortem in the next couple of days.
Overall i’m pretty happy with the outcome and experience of this project. It has taught me a lot about technical design and that sometimes pushing yourself for better things can be difficult but extremely worth