Friday, April 17, 2026

April 2026 TBS Update

I've been building this XCOM-style game on and off for several years. Last night I hit a milestone - completed a full game loop for the first time: strategic layer, soldier loadout, deploying into a tactical battle, and processing the results. It's still all simple objects, but there's a lot of underlying work in FOV, pathfinding, and all the systems that tie it together - many of which I've been working on since the beginning of this blog. 





Thursday, October 7, 2021

My development workflow

Today, I'm going to demonstrate my developer workflow and architecture for my game, with Unity3D, GitHub, and Visual Studio. I've created a workflow that allows me to verify any change with a rich set of automated tests, and automatically load those changes into my Unity project, reducing the time to resolve bugs, and giving me confidence that my code is robust.

Architecture

At the top level, I have my Unity project (in green), a private project stored in GitHub. This has an attached .NET project, (named the default "Assembly-CSharp"), that references a .NET standard dependency "Battle.Logic", a public project in GitHub. Attached to the Battle.Logic project, is a unit test project that currently has ~97% code coverage - and leverages over 200 tests that run in just a few seconds - giving me almost instant feedback that the project is in good shape.


The bug

Let's look at the bug. I've been iterating on AI for a few weeks, but when this code runs in Unity, it fails with the error below. You'll note the error is in the Battle.Logic. Uh oh. How can I replicate this? Luckily we can do it in just a few minutes. You'll notice in the top right of the UI, I have a "Save JSON" button. What does this do? Let's press it and see:



The button serializes our game into a JSON file. In the console below we can see where it was saved on our laptop, and copying the file into our Battle.Logic test project, I can create an automated test to load the file, replicate the game state, and test it.



The code to do this is quite simple, I open the JSON file, add the correct line to replicate the issue we saw in our Unity file (run the AI). Look at the error in the bottom left of the tests- we get exactly the same error we saw in Unity. Hurray! Now we can troubleshoot and resolve the issue. In this situation, an object, (character "CoverState"), wasn't being initialized correctly. I resolve the issue, my test passes, and I can continue building my game.



Let's look at how we get this fix back into Unity. In GitHub, we open a new pull request with the fix. We have a couple other checks in here to confirm quality - with code coverage (Coverall) and code analysis (SonarCloud). Everything looks good, so we merge the Pull Request.


This triggers a new build on the main branch in GitHub, which when successful, publishes a new dll to a "release", with a unique version - in this case 0.9.2. 



To get this into my Unity project, I wrote a simple script to download the files from the latest release, unblock them (thank you Windows, but I trust my files), and copy them to the right place in my Unity project



Opening up my Unity project, I can see in the files in the "Git Changes" box.



Running the same situation as before, I can see the issue is resolved - although I did find another bug - my character AI moved it to an unexpected position. Time to do this again, and write a more comprehensive test for this scenario!



Wrap up

Just a simple example, but I value my time, and developer productivity is everything. In just a few minutes, with some key enabling automation, I can troubleshoot and resolve an issue, create a test to prevent it from happening again, and get the change back into my Unity project in just a few minutes.

Sunday, August 1, 2021

Reorganizing, again.

Every couple of years I end up in the same spot, lost, overwhelmed, unmotivated. 6-12 months later I repeat the cycle, rebooting and starting again. I've done this for roughly ten years... 

This round is different. This one is powered by DevOps and GitHub.

Why is this one so different? 

First, I've separated the logic into a separate project, and it's open source and on GitHub for anyone to use. Using .NET Standard 2.0, and C#, I'm able to write unit tests that validate that this project is working as expected. As it's all just C#, no graphics are being rendered, the tests run extremely fast, nearly 200 tests in less than half a second. I calculate paths, chance to hit, field of view, etc, without a graphics engine. These automated tests are a crutch when I walk away for a day, week, or several months. I know that when I return, I have a series of tests to tell me when I break something else. 




This also allows me to measure code coverage - the percent of code that has a test. My code coverage is very high, 99.61%, when 70-80% is typical.


Additionally, I can visually see my maps and situations in 2D using ASCII. I can render these data structures in 3D with Unity3D relatively easily, but seeing it in 2D gives me an easy debug mechanism. 




I also have a detailed log to help me with debugging. Human readable, important as I am a human. 


More is coming, I've just started to integrate this in Unity3D, and I know there will be more breaks in development, but now I know I'm chipping away at a system I can always pick back up again. I start a turn by calling my battle logic, all of the movement calls return a list of moves and if their are any interrupts - and in general, it's making the 3D part of this, much, much easier to manage




Tuesday, September 26, 2017

Update 51: Emerging AI

Next on the list was AI. I had a plan for this. Since most interactions are over in 2-3 turns, I don't think I have to worry about a higher AI (where the AI is trying to think long term). I think, (at least initially), I just want to encourage the AI to move towards the player and be fairly aggressive, staying in cover and shooting if the chance to hit is high. These ten rules are definitely just a starting point.

  1. Find all possible tiles the character can move to, initializing them with a score of 0.
  2. Assign points to any tiles that are in cover (+10). If the character is currently not in cover, give all of those positions an extra boost to encourage the character to move into cover. (+10)
  3. Add points for tiles that are in range for the character to shoot at the opponent. (+10)
  4. Add points for tiles that close the gap between the AI and opponent. (+5)
  5. Add points for positions in cover that flank the opponent (+20)
  6. Add points if the opponent is within range and has more than a 50% chance to hit. (+10)
  7. Add even more points if the chance to hit is 80% or more. (+20)
  8. Add even more points if the chance to hit is 95% or higher. (+30)
  9. Order the list by total score, descending order (highest first)
  10. Pick the top score from the list. 

When I add difficulty levels later, I will randomize the top 3-5 scores and pick one. This will help to give the AI some unpredictability and ensure that it's not always brutally taking the best position. Everyone makes mistakes, and it's important the AI does too (except on maybe the highest difficulty level). I may do this sooner than later to keep things interesting.

This AI is currently working, but I have some timing issues, where the AI moves into cover, but fires his weapon for his second action before he has completed his first action. 

Here is a series of screenshots showing the AI's at the beginning of a turn (standing out of cover), and then moving to cover and shooting at the (flanked) player. 

This one is showing the timing issues I have, where the AI fired (action 2) before completing his walk to the new cover (action 1), in the process destroying the cover.




Next I'm going to tweak the timings so the actions are one at a time and add side steps to the characters so that when behind cover they don't shoot out their own cover, but step to the side and shoot around the cover.

Thursday, September 14, 2017

Update 50: Combat and 50!!

5 years and 50 posts later, here we are, still working through the combat mechanics. Amazingly, I'm still making regular progress. I think the keyword here is regular, there have definitely been some large gaps in progress.

However, the combat is proceeding along nicely. I've added projectiles and the destructive terrain back. I've added back the shoulder cam when you are aiming and choosing your target. The enemy and player are now dealing (and receiving) damage. I almost have a quasi game here. 

Here you can see the setup of the level, with the second (shoulder) camera ready



The view from the shoulder cam. The models really look low res here!

Boom!


 Before the shot...

"Missed", that is the end of that wall!


Here you can see the ray-cast passing through the enemy, it was a hit!


Next on the list is basic AI, it's time for the enemy to respond to your actions and try to defeat you. 

Thursday, August 31, 2017

Update 49: Added weapons to the characters

I have now added weapons to the characters. The Simple Military pack has a variety of weapons, but at this stage, I've assigned a 'modern assault rifle' for the 'good guys', and a AK47 for the 'bad guys'.




I also added a muzzle flash when the gun shoots (it shows for just a fraction of a second).




Next I will add projectiles and add back in the destructive environment that I started with, all those years ago.

Sunday, August 27, 2017

Update 48: Implementing the turn state diagrams

Next I've been working on implementing more of the game, which is a big departure of most of my posts, where I've been working on game mechanics, usually in isolation. I'm calling this the "player state diagram". You can see this represented in a state diagram below. Also encased in the diagram is a road map of the features I've implemented and what I plan to implement next.




When a player has finished all of their moves, it switches to the enemy turn, where the (overly simplified AI) decides to just pass ("Patrol"), and switch back to the player. Since the player still can't attack, I think this is fair. 

Next we need to add AI to the player to move into cover and shooting between the two teams...

Tuesday, August 22, 2017

Update 47: Cover and Flanking

In my last update I showed a screenshot with a soldier in cover. In this latest update, I've added an enemy and added the code that marks the players as in cover or being flanked/out of cover.

Here my player is in cover (the enemy has a lower chance of hitting the player and dealing damage):




Here my player is flanked and not in cover (the enemy has a much higher chance of hitting the player and dealing critical damage):


There was actually quite a lot of rework involved in the code and corresponding systems to get the enemy soldier to (re-)appear and have everything work. The next update will be even more involved as I refactor the code into more systems and add more players and enemies - adding turns that switch back and forth after movement.

Monday, August 14, 2017

Update 46: OpenTile

Two years can really fly by. Growing families, big work projects, small home projects, and as many distractions as you can throw at a sticky wall, here I am. I haven't been doing nothing, I just haven't been writing about it. I'm trying to change the not writing about things problem today.

One of those mini projects I've been working on is very relevant for this project. About a year and a half ago, I started a GitHub project I've called OpenTile. OpenTile is an opensource solution to create the building blocks and data structures for a tile based game. There is no UI, apart from the ASCII files, but it's being built in C#, with a branch for Unity3d. There is currently functionality for basic path finding, possible available tiles (for movement), cover, as well as a number of utility functions. 

I've found this framework really helpful for building a game. I'm able to focus on fundamental functionality such as path finding or cover in a simple ASCII UI, and then it's much easier to integrate the changes into a full 3d engine - with the confidence that it works.

In the screenshots below you can see two simple examples that show the output of path finding on a large map with varied obstacles.





In Unity3D, this is what the result looks like on a small test map:





Friday, October 16, 2015

Update 45: Rebuilding random levels

Using the new resources I have, I've rebuilt the level generator, generating diorama levels with a wooden border again. I was pleasantly surprised how well the level generation code looked and worked, after not looking at this code for a year. Here are a few screenshots showing the progress. At this point I only have 5 variations of tiles, so there is a lot of repetition, but it's easy to add more tiles. Here are some screenshots: 








Next is path-finding!

Friday, October 9, 2015

Update 44: Splitting the Engine

XCOM is obviously a huge influence to this project, both the 1994 and 2012 versions. In a recent interview, XCOM designer Jake Solomon talked about a "Cinematic" piece of the XCOM engine. This was a really insightful and interesting way to think about how the player is immersed in the game via movie like cuts that are interlocked throughout game play. The idea is that when you have a move, the engine, (I'm going to call this the "Interaction" engine, as this is where the player makes choices and then observes the results), calculates the result of, essentially, the next 15-20 seconds. This is then fed into a "Cinematic" engine, which moves the camera around to create a movie like feel. This is what puts the camera behind the player to shoot, moves the camera to see an enemy when a overwatch action occurs and helps to show the 'killcam'. This also helped to explain a few glitches that happened where you would sometimes shoot through cover, probably because the engine considered the cover to not be in the way.

For example: A player decides to move to another location. The interactions engine figures out along the players path if anything will happen (enemy overwatch) and predetermines all results. (perhaps in this case, an enemy will have an overwatch event, shoot at the player and kill them mid path). This information is all passed to the Cinematics engine which controls the result. The camera would start by showing the players movement until it reaches the point of the overwatch event, where it would move the camera to show the enemy firing and then move again to show the player dying.

I really like this idea and think there are a lot of benefits in implementing it. You are essentially making it easier to test, (as you can setup the same set of events and reply them in the cinematic engine), as well as really separating the core mechanics of the game from the visual representation.

I'm really leaning towards trying to implement my own version of this, and this is the perfect time to integrate it in. Anyone else thought of an idea like this before? 

Monday, October 5, 2015

Update 43: Rebooting again

As I said at the beginning of the project, "this is a personal project and there will be breaks..." Sometimes it was days. Sometimes weeks. Occasionally a month. This time it was a lot of months. However, one of the interesting things I found about this project is I never stopped thinking about it. Most of the delay was caused by work and family commitments, but everyday I thought about what I should be doing next... 

Today, I'm back with a new reboot, using some assets from a "Simple Military" unity package, made by a New Zealand company called Synty Studios.


This in combination with a new design idea, (Hovertank Wars is dead again), still based on turn based combat (and XCOM), but set in a new environment that drastically reduces the scope of the project. and I think makes it all possible. Over the past three (!!!) years I've now created a turn based system with pathfinding, a UI, a dynamic camera system with killcam, loading/saving, destructive environments, random levels and even a basic AI. 

It's time to take all of this and create a complete game. I look forward to (re-) starting this journey again!


Friday, April 3, 2015

Update 42: Unity 5 and Unity Cloud Builds

I knew I would vanish for a few months eventually - the Christmas break last year was apparently the trigger. I haven't posted for a while, but I started development again about a month ago when Unity 5 was released.


Along with this was a new cloud build system that works with GitHub


This is a pretty neat feature that allows me to automatically build and deploy web versions of my game.


This is big news for me as it means I have a more repeatable and independent process. I have a few more hours left to transfer over the content into my Unity 5 project, and then I'll start implementing some new features.


Friday, November 14, 2014

Update 41: Code Reorganization

This last week has been productive and I have almost completed path finding! This involved a complete reorganization of the code files to implement namespaces. The new structure really enabled me to connect all the pieces together so that I can now click around my levels, moving my units from side to side. My new namespace structure looks like this:

Characters

--Actions
--Pathfinding
--UI
Common
LevelEnvironment
Main
--UI

I debated having UI at the root level with "Main" and "Character" children, but ultimately decided that these UI elements would be completely different. The diagram below should what the code map looks like after the new reorganization:





I've also developed a process to implement new features in a safe and comprehensive way, using several levels of prototyping. This is similar to branching, which many software development processes use (but I do not). My process usually starts with an almost blank level. Once the feature has been implemented, I usually integrate my new feature up to one of my intermediate test levels, testing in a controlled environment with a 
few buttons to test movement and shooting. This is typically my 'tank test' level which has a few static objects and buttons to constrict movement, almost in a unit test sort of way for regression testing. The final step of my process is to integrate in the feature with my actual game levels. This means it can take a while for a new feature to make it into the game, but it's also a comfortable way for me to test new features and not put them into the main game until I am ready. 




I've been using this with path finding really successfully, which is why I'm confident I will have it complete by the end of November.


Finally, I have the bug of the month. In the top design view panel, you can see an in progress picture with a sample level. All of the red nodes indicate areas that are not-walkable, generated by my path finding. In the bottom game view panel , you can see that... I have no grass/ground. Where did my ground go? It turned out it was a relatively simple problem, my camera had turned off the option to show the ground, but it did create a few interesting levels...




Thursday, October 30, 2014

Update 40: Dynamic Pathfinding

The last two weeks have been focused on getting a basic dynamic path finding algorithm to work. This includes getting the object to follow the seeker path, and allowing for dynamic environments.

The first screenshot shows where I've been up until today. The path finding is 'baked' into the screen and there is a path from the beginning (green gate) to end (red gate).



The second screenshot shows me what it looks like with some dynamic objects added to the scene and dynamic 'baking'. Here I can see that my green path adjusted to avoid the new dynamic obstacles and created a new path to the end gate.


The ultimate test of this is to move the end gate to the other end of the screen. 



This is all good news. Now that my prototype is working, I can integrate the path finding within my game.

Friday, October 17, 2014

Update 39: Exploring Pathfinding

Another week, another round of bugs fixed.

  • Added some basic sounds for explosions and shooting
  • Fixed some bugs around shooting when I missed. The bullet would stop at the target, instead of continuing into the sunset. 
  • Upgraded the shooting range modifier using the formula:
    • RangeModifier = 50 - ([tiles between source & target * 5)
  • Upgraded the AI to choose the best target based on the difficulty, instead of taking the first enemy off the list
  • Started work on creating a path finding solution. I'm using the Aron A* Pathfinding Project. You can see an early prototype below. The first screenshot shows no smoothing, the second with smoothing.




Friday, October 10, 2014

Update 38: Adding messages and more polish

This week I added a messaging system and fixed bugs. The messages show a status with showing a hit or miss with each shot . This really ties each action to a result, which I think is important.






I also have a few bloopers. This is a result of my very simple movement AI. When two tanks get close to one another, the physics pushes one of top of another, in a manner very similar to a tectonic plate in the earth's crust, but creating some humorous situations.





Next week I'll have some initial path finding results, after having some success this week using Aron's path finding plugin.