The Factory

Not much, but I’m quite proud it works well.

In the begining I implemented a simple Factory that had to know what exactly it’s doing (a.k.a. methods like “CreateThisObjectPrecisely()”).

After some tweaking I finally managed to implement the proper library – a bit generic and reliant on an “IProduct” interface. The downside is that (at least for the moment) it uses only enpty constructors for the data classes, but that may change later.

A very persistent idea of mine is to fonally create a truly generic factory that dows not care at all what it’s producing, but the real deal will delve completely into reflection and I am not ready for that yet.

All in all – I am quite happy with the result so far.

Oh, one other thing – it’s a static factory that works with a singleton PoolManager, so garbage collection is taken care of, as much as possible. So far I Pooling is may be the most important game design pattern I have come across.

TankUp: State Machining the Behaviour part 2

I really want to make a decent state machine. I think the proper way to go is modular, eventful and probably recursive . The idea of really flexible state machine is… tantalizing, but unfortunately I still can not see t completely and, truthfully, TankUp will not utilize it fully. I don’t have a lot of possible states and they are, for better or for worse, hierarchical. The best I can get is a fairly bonsaical tree.

The core of my decision making is something like this:

conditionreaction
More than 1 enemyflee
1 enemyfight
Upgrades and Layerschoose layers
Nothing in rangeroam

Of course, this can be (and will be) elaborated upon by adding more precision to the conditions. Also… The best idea I’ve had so far is to add another layer to the behaviour.

All a tank can do is fight another tank, flee the scene, pick upsome asset or wander about, looking for trouble. However, the player is bound to have some more information about the battlefield than the autotanks (he sees further, for example). So I don’t want the player to see two tanks that appear to be in range of each other and immediately know with certainty what they will do. Some variability goes a long way.

Enter stances.

There are three primary stances – aggressive, balanced, evasive. Each autotank has one stance associated at birth. Shove that into the table and you get to that:

conditionAggressive reactionBalanced reactionEvasive reaction
More than 1 enemy, total size larger than own sizefleefleeflee
More than 1 enemy, total size smaller than own sizefightfleeflee
1 enemy, larger than selffightfightflee
1 enemy, smaller than selffightfightfight
Upgrades and Layersupgradeslayerslayers
No assets in rangeroamroamroam

You can see some real behavoiur now. Or a semblance of one, at least.

And if you’ve gone that far, there is one more step, that costs nothing, but adds a lot – two extra stances at each end of the spectrum and a simple mechanism for changing stance.

conditionBerserkAggressiveBalancedEvasiveCoward
More than 1 enemy, total size larger than own sizefightfleefleefleeflee
More than 1 enemy, total size smaller than own sizefightfightfleefleeflee
1 enemy, larger than selffightfightfightfleeflee
1 enemy, smaller than selffightfightfightfightflee
Upgrades and Layersupgradesupgradeslayerslayerslayers
No assets in rangeroamroamroamroamroam

Each stance sets two variables – upper and lower threshold for the autotank size (in terms of number of layers). Let’s say for balanced the upper bound is 4, the lower is 2.

For now (cause it’s untested) the lower bound is not taken into account until the autotank has reached it (see “Speed”, “50mph”). So the autotank is created, starting with 1 layer. If it collects another one, the tank gets in it’s own zone (I need a term… ‘native’?). If the tank picks up another 2 layers and gets to 4, he changes the stance to aggressive. If he drops below 2, he becoms a coward. Those stances dictate the reaction to his surroundings.

Important note about the tanks upbringing:

Let’s take an aggressive tank for example. The bounds are, let’s say, 6 and 3. So if an aggresive tank drops below 3 layers, he becomes balanced. However, the tank that was born balanced needs to drop below 4 to transition from aggresive to balanced. So the native stance matters. That should keep things interesting.

And of course, there is no upbound transition for the berserk, just as there is no downwards transition for the coward.

TankUp: State Machining the Behaviour

About the state machine.

I’m working my way towards the state machines that should govern the behaviour of the autotanks.

The main problem that I had was that generic tutorials and explanations of state-machines always start from a graph and describe a small number of states with transitions depending on a couple of variables at most.

Not that this is bad in any way, but I would not manage to describe what I need with this kind of constraints.

Well, given the fact that in the end each state will surely be linked to at least one other state and that, provided you don’t mess up something, you will be able to find a path from any one one state to any other, following the liks, you will have the graph as a result. If you try to start with a graph and multiple conditions, it is too easy to end up tangled up.

So… let’s start with all the conditions I want to keep in mind.

The AI tank can see other tanks and the pickups (within its sensor range). It can not assess anything else from an opponent than its size. So the stack size will be passed with the scanned data.

When the AITank (I’ll have to call it ‘autotank’ from now on, cause it’s easier to type)… so when the autotank gets information that there are enemies and, let’s say, a layer in range, it has to decide what to do.

The possible options are, as it can be expected: fight one of the enemies, go for the layer or bugger off.

However, I wanted to have the autotank act differently depending on the number of enemies, for example, or the size of the whole ‘gang’. It’s not that ‘enemy in range = attack’ is a bad pattern, but it’s suitable for large mobs, not individual combatants. Also, I don’t want to have super smart autotanks, but I need then to make sense.

Here’s what I have in my mind at this point…

(to be continued…)

TankUp: Describing the environment, part 2

What I’ve been pondering about the last few days is the question how to build a fingerprint of the environment of the AI Tank, once its sensor has taken down the data.

For now what the tank is interested in is the following:

  1. how many enemies are in teh vicinity (in sensor range)
  2. what is the total size of the enemies
  3. how big is the biggest enemy
  4. are there any layers in range
  5. are there any upgrades in range

Once I have the data I’d like to boil down that into a simgular piece of information that I can pass around and compare through time. Obviously an object would be… adequate. May be even a struct, as this is only a collection of data with no behaviour. However, I might still need a fingerprint I can fit into a single value type variable – Iwant to be able to easily compare it’s contents in t-0 and t=1 and say “something has changed, make a desicion about a possible change of behaviour”.

My usual go, given the fact that all the relevant information is expressed in integers, is to line them up one after another and read them as one larger ingeter, but that, for the moment, seems clunky. Is it?

TankUp: Describing the environment

Concerning: The way the AI Tanks describe their environment.

I just put in place the class that will be used by the tanks to note down the specifics of their environment. An instance of that class, filled in with a couple of GameObject lists (one for other combatants, another for pickups), a simple int for the current stack size of the tank and (very important) a fingerprint of the entire content of the environment object instance.

That last part is there to avoid unnecessary shuffling of data. The tank will always keep the object up to date in order to pass it to the State Selector static class and get back a verdict on whether state change is needed. However, I’d like to do that only if necessary. So at regular intervals the tank will refresh its environment object, get the fingerprint and will bother the state selector only if the new fingerprint differs from the latest one. This seems to be a reasonable way to do it.

Also, about the state selector – a static class seemed fine. No state of it’s own (how ironic 🙂 ), something like ‘WWJD’ – what state would you choose if you were me and this was your immediate context 🙂

P.S. Something is wrong with the whole thingy – wether the IDE or Unity is behaving. I updated Unity to 3.0f6, then VS said – what is this ‘TextMeshPro’ you speak of? I expected a whole metric ton of .csproj files to have already been loaded for this project. Therefore you have 200+ errors. The game project is running, though, so I don’t know… I dialed it back to 3.0f5 and the problem remains. We’ll see when I try it at the PC at home.

TankUp: What I have so far (Part II)

The Tank Data:

The whole spiel of the game comes from the stack of layers that the tank in question has at any moment.

The most fun part was the size. Each tank has a default size (logically) and they are all the same. And, as per the initial idea, accumulating layers had to make the tank less effective, to negate the positives of this accumulation.

The damage factor is easily calculated (even though I am still not committed to a final formula), but I wanted also to give some visual reference for that stack size.

The most obvious choice was to increase the size of the tank itself. This has two advantages – first, the player can infer some stack sizes by the size of a tank (either your own or of another combatant unit); second – larger tank should be easier to hit, which directly influences performance.

The most obvious issue is that, of course, linear relation between stack size and tank size does not work. A simple quadratic relation (check (x^2)/20 for example) would stunt the tank growth in the lower stack sizes. However this leads to a blown-up tank size the more layers you accumulate. Luckily, I remembered that the function form I need is called ‘histogram’.

Basically I wanted to allow the tanks to differ little in the lower range (up to 3-4 layers), then they should be allowed more noticeable growth, and finally (very important) – reduce the growth factor after some level. Diminishing returns after a certain number of layers would guarantee that there will be a soft cap on the size.

The most important effect of the histogram? – You can easily discern three groups of tanks – small, medium, large. Additionally, if the final equation allows it, a player may be even able to tell the difference between different sizes on the steepest slope of the curve – the lowest sizes are more or less indistinguishable, the highest – too. But in the middle you have noticeable jumps up and down when you add/remove layers.

So how do you find exactly what you need?

First – wikipedia, for list of example histograms

Second – Desmos.com (https://www. desmos.com/calculator) – you type the formula, then you start fiddling with it to make it behave properly. As the minimum number of layers is 1, I had to move the histogram around to make it cross approximately 1 on the x=1 axis. This, of course, was eyeballing it, so:

Third – place whatever version of the equation you’ve got, replace the constant you’ve been fiddling with ‘y’, take that to WolframAlha and ask ‘what would ‘Y’ be if x is 1. Much obliged!

I tried a few histogram equations and I’ve settled on one for now. I am still not sure it will be the final one. The current one settles at about 2.6 something as an upper bound (I can’t be bothered to check if it really converges, as it seems to be the case). I would much prefer the soft cap to be somewhere about 2, but more important for the final decision would be the position of the steepest slope. And I would know what stack sizes get you there when I can actually test some interaction. So, for now, I am sticking with what I have.

Resources:

Wikipedia – but obviously!

Desmos.com – simply delightful; exactly what I needed for this case (including graphing multiple equations for comparison and proving some export options)

WolframAlpha – for when you put your head in a bucket of math that is above your level

TankUp: What I have so far (Part I)

Construction:

Of course I started with the player tank. I am still not entirely sure if I have structured the game object of the tank in the optimal way. Later in the process I stumbled upon this Jason Weimann video (https://www.youtube.com/watch?v=UWGspLJ-_8U) and I will surely revisit the components structure of the tanks again at some point.

For now, I think the component model is decent – box game objects, which contain the elements in a small hierarchical tree (you gotta keep them separated!). The most important questions were:

– who holds the tank data structures

– how much of a separation do I need for the whole tank model and it’s turret

As this is my first experience with mouse control, the turret was a bit tricky, as it had to be controlled by the mouse, while still being part of the tank itself, which is controlled by the keyboard.

Later on, when I get to add animations, I will probably have to separate the tracks from the chasis in a separate objects, contained in another game object, in order to be able to stick an animation script to that object, which will listen for the current speed of the tank and the motion of turning around. A sync with that would be good, although it will probably be not very visible, given the position of the camera and it’s distance from the model.

Data:

The data structure itself works well and I think there will be no more big changes in that department:

– one class for the layers – each layer has only a weapon type and health.

– one class for the layer stack (TankData) – in addition to the Stack<Layer> structure it has a bunch of methods for extracting stack stats – the size, balance, top layer weapon type, etc.

– a ‘Tank’ class – this one is MonoBehaviour, as it is attacked to the tank game object. This is the main script that communicates with the turret, the mover scripts, behavior scripts, etc.

– the turret has its own scripts – a main ‘Turret’ script that for now only switches the visual model to match the weapon type of the tank; it has also one script that keeps the turret pointed to mouse cursor once I get to tackle shooting, there will be a script (or, more likely, series of scripts) to handle that – each of the weapon types will have distinctive shooting style and that will add some more structure to the data base.

= = =

Problems:

* One problem is the proper game object and component hierarchy. I mentioned that I am not sure the current GO/Component structure of the tank is perfect. I hope I will not find later that I need to make drastic changes, but this seems unlikely. Using game objects to hold collections of other objects is a fine way to approach complex models. I like to think that if you are unsure how to handle a concrete model structure, a good way to think about the model is to imagine it’s all regular C# data classes – so just follow the ‘Single responsibility principle’ from SOLID.

* The movement of the turret was a hell of a ride. And I am afraid it is not over yet. From afar it appears that it faithfully follows the mouse position, but looking closely reveals that the turret does not rotate around the proper point (the center of the turret itself) – instead it rotates around the midpoint of the whole object, which includes the length of the weapon itself. I am yet to research how to approach that. Also… there’s a design problem I haven’t tackled yet – the weapon does not tilt, which means that I can not simply shoot forwards from the respective gun barrel. For the gun and the rocket I can apply ballistic trajectory, but what about the raygun – it should be simply a raycast. If your tank is on an upwards slope (for example) and the target is on a plateau in front of you, you will simply shoot above it. For a game of this type I will obviously have to cheat in some way, but how exactly… uncertain. Should I lower/elevate the vector, depending on the circumstances? If so – should I move the visuals of the turret also? For steep angles that might look bad. If I don’t, it might look bad to have the beam shoot from the raygun ‘barrel’ at an angle. We’ll see.

* One more thing about the rotation of the turret – oh, my! There are (of course) more than one way to rotate object X towards object Y. As most of them use Quaternions and that part of math I do not wield, I had to simply try every solution I found. For the life of me I cannot tell what I used at the end without checking my own code. One of the big problems is that (because that math is not very accessible) when people look for help in StackOverflow, some good soul gives a solution (sometimes as generic code, sometimes more tailored to the specific example), but usually there is no explanation ‘why’ exactly this is a good way to implement the rotation. And inferring that from the example in question and figuring out how to translate that to your own case (if at all applicable) is… an adventure 🙂 Anyways, I’ll get to know that better in time.

Resources:

I’ll have to add a section resources at some point, but for now – here’s the one resource I managed to mention here:

Jason Weimann – “Unity3D Architecture – Where should I put my components? (and a little Weapon Swapping)”

https://www. youtube.com/watch?v=UWGspLJ-_8U

P.S. Let me say in advance that Jason Weimann is one of my primary sources. I cannot recommend his channel highly enough. The same goes for quill18creates.

#SimplyPriceless.

There are others also, but those two are my most common youtube sources and are the absolute go to!