Soccer game development concepts

This document applies to team-based ballgames in general, but uses soccer (association football) as use case.

1. Introduction
3. The ball
4. Animations
5. Useful calculations
6. General positioning
7. Defensive positioning
8. Offensive positioning
9. On the ball decision-making

Revision 7, last updated: December 30, 2021

Introduction

Team-based ballgames can prove difficult to program. The proper application of animations in relation to gameplay may feel like black magic, while tactically, defensive positioning specifically can be hard, as mistakes can lead to easy goals, which aren't much fun to either score or concede.

I have developed 2 soccer games, Gameplay Football and Ballsy! World Cup 2020, which I will refer to throughout this document. I've put in a lot of time and effort to come up with creative solutions to the problems I ran into along the way, and hope by sharing some of my insights, I can spare others some frustration.

Gameplay Football (source, binary downloads) didn't get past the playable demo stage, but is quite complete in the gameplay department. It uses a realistic style and rules. It's a bit older (2008-2015) and uses a custom engine. It was discontinued for various reasons, among which; using a custom engine and toolset = hard to maintain, scope creep, messy codebase.

Ballsy! World Cup 2020 (steam link, itch.io link) is a more cartoony, retro-themed game. It re-uses many of the concepts from Gameplay Football.

The ball

The predicted future ball position at various timestamps will be very important later on, to calculate how long it will take players to get to the ball, and thus decide who is the best candidate to go there, what team will likely gain ball possession, etc. If we calculate the ball physics ourselves, we have precise predictions on its future positions and velocities. This is the setup I ended up with, which has worked great for me:

  1. Have an array (linked list might work well) that will contain ball position & velocity for each future timestep, up to 2 to 3 seconds from now will do. To support curve balls (swerve), include rotational velocity.
  2. Have a function that calculates the values in this array, with a starting timestamp as parameter (this parameter will come in handy in the next step). This function iterates over the array, doing the actual physics. It calculates each entry's position and velocity, based on the previous entry's position and velocity, whilst taking into account things like air resistance, rolling resistance if the ball is on the ground, bounces from the ground / goal posts, swerve..
  3. We don't have to recalculate this whole array every physics frame, as long as the ball doesn't get touched; just, for each physics frame, remove the first entry of this array (as it represents the previous frame, we don't need that anymore), and move the ball object to the position in the new first entry of the array. Now we add a new entry at the end of the array, this is the only entry we need to recalculate; we can use the aforementioned timestamp parameter for this. Basically, we have just shifted the predicted timeline over the whole array one frame towards 'now' by removing the first entry.
  4. Every time the ball is actively touched by a player, all the future predictions will be void. So, update the new ball velocity in the first entry, and recalculate everything from there.

Now, we can simply create a function that returns the predicted ball position and momentum given a future timestep as parameter.

Animations

Animations are what (imho) complicates things the most. It is the main reason I switched to making a sprite-based football game. Still, I got the animations to work quite well in Gameplay Football. Keep in mind, this was years ago, using my own tools; it could well be that commercial game engines include animation tools and concepts that make my knowledge obsolete.

Creating animations

There's not much I can say about creating animations; I made the mistake of making my own, archaic, animation tool, while better tools existed. Also, I used the 3D Studio Max .ASE (ASCII format) export, which wouldn't export joints/bones/skin information, so I had to awkwardly encode this information inside the vertex colors. In short: don't do this; use a proven game engine, OR at least a proper professional animation library.

This also means that I would advise against using Gameplay Football's player model and animations. You could still write a tool that imports my animations into a better system, but keep in mind the joint setup of my players around the pelvis/hip area isn't ideal.

What does an animation look like?

In Gameplay Football, most movement animations consist of 1 step, starting with the right leg behind, ending with the left leg behind. On loading, a mirrored copy is made as well. More complicated or longer animations can consist of multiple steps. The number of steps should be included somewhere, so the engine knows on what foot we start/end the animation, since it's preferable to have consequent animations start off on the correct foot.

Animation-based vs. physics-based systems

There's various ways of going about player positioning/movement in a soccer game. From here, the term 'player' refers to the models running around in-game, while 'controller' refers to the human or AI that controls this player.

  1. Animation-based: The best-fit animation is chosen based on controller input and the situation of the player. The animation plays out, and when it's finished, a new animation is chosen; rinse & repeat. Movement possibilities are constrained by which animations are available.
  2. Physics-based: The movement of the player is fully controlled by physics calculations; animations that best match these movements are then 'overlaid'. Movement is constrained by what the physics system allows.
  3. A hybrid solution: though the best-fit animation is chosen like in the animation-based system, it can be altered on-the-fly in velocity and angle by a physics system. This can be helpful in making movements more fluent and controllable. For example, a player could be slowed down if they have a low 'velocity' stat. Another example; hand-made animations can be a bit inconsistent in velocities, cornering speeds etc.; this can be corrected by having it constrained by a physics system, so we never sprint or corner faster than is allowed by the gameplay designer.

During development of Gameplay Football, I tried a variety of different combinations, ending up with a hybrid solution, leaning towards physics-based. Movement animations could get interrupted halfway if the controller inputted a wholly new direction or velocity to make the game feel more responsive.

I'd argue that physics-based solutions are generally easier to work with and result in better gameplay, while animation-based solutions are the best to look at. The problem with a fully animation-based system is that one needs to make *a lot* of animations to have the game play well. For example, when playing Pro Evolution Soccer 6 (the oldie from ~2006 or so) - which is mostly animation-based - there were certain situations in which the player hopelessly mishandled balls that would be easy in real life, simply because that certain animation was missing. After all,

The amount of animations needed to cover all situations is staggering

Let's say a player's physical possibilities are quantized into these possible states (this is what I ended up with in Gameplay Football, but it's arbitrary):

To just cover the combinations of these states, we already need an insane amount of animations. And that's just animations that start and end in the same state. To cover everything, we also need an animation that starts standing still, ends walking; one that starts standing still, ends walking while being rotated 45 degrees; one that starts standing still, ends walking while being rotated 45 degrees, while making a 90 degree corner, ..this is becoming a problem.

There's various ways to go about this; in a hybrid system, we could get away by making some key animations, and then play the animation that fits the desired action the best. For example, we could have a shooting animation that starts at walking speed, but also use it while sprinting, by just having it move faster than the animation was made for. We could have the code decide that a certain action is possible, and then force the closest fit animation onto it, and adapt it to our desired quantized incoming/outgoing states. Play the game and pause everytime you see something that doesn't look right at all; this denotes an animation that is sorely lacking. Create the animation, rinse, repeat.

In case you are using the Gameplay Football source as reference, keep in mind I named the 'slowly walking' state 'dribble', which is confusing. Sorry about that. It has nothing to do with ball interaction, just movement tempo. Anims for receiving/controlling the ball are named 'trap' (for 'trapping the ball'), which I should probably have named 'receive'. Ah well.

Automatically generating (most) movement animations

(By movement animations, I refer to animations that do not touch the ball in any way)
A lot of movement animations are very samey; walking forward and running forward; making a 20 degrees angle or making a 45 degree angle, etc. This gave me the idea to generate the bulk of the movement animations using interpolation. This is how it works:

  1. Have a collection of 'template animations', 1 step long, that start and end in the same state, no cornering. For example, 'walk 1 step forward', 'walk 1 step forwards at a body angle of 45 degrees', 'sprint forward', 'stand still' etcetera. All these animations have the same amount of frames. (Keep in mind, the animations at a non-zero body angle need both a left-footed and a right-footed version, while forward oriented anims get this for free by mirroring)
  2. Iterate over all legal combinations of 2 animations; 1 will be used as starting situation, 1 as end situation. Some can be culled, for example, maybe we can't go from standing still to full sprint in one step, so we can filter these here.
  3. For each combination, overlay both animations into a new animation, interpolating all joint angles and body position/rotation, so that it starts out being the first animation, and ends up being the second animation. So we now get, for free, an animation that accelerates from standing still to moving forwards, an animation that walks forwards straight ahead and ends up walking forwards with a 45 degree body angle, etc.

For Gameplay Football, I ended up using just 10 template animations to generate the bulk of the movement animations. This was a huge improvement in automation and saved me countless hours of time. I still made some animations manually, because not every combination can be generated through interpolation without looking funky, so there's still 49 manually made movement animations in there. I basically just generated everything first, played the game until I saw some anim that didn't look right, then created that one manually, sometimes using the generated version as a base.

How to select/use ball touch animations?

In my setup, animations that touch the ball do this by denoting the desired ball position at one specific keyframe, usually halfway the first step. Where the ball goes from there depends on the type of animation, and isn't 'encoded' in the anim - only the desired ball position is. Another possible setup would be to fully animate the ball in the animation as well; I have no experience with this setup, so I can't say anything about the up- and downsides (an exception is the keeper handling the ball, where the ball simply sticks to the keeper's hand position until he releases it again, though this stickiness happens in code, not in the animation). Some of the following paragraphs are only applicable in the 'one keyframe' setup.

While running around using movement animations, if the ball is close, we can make a selection of what dribble animations fit the controller input. Or maybe we pressed the pass button, and add a selection of pass animations to consider. Lets say we just want to dribble forwards at a certain velocity, so we end up with one candidate animation. We can now periodically check if we should interrupt our current movement animation, and start this dribble animation. So how do we check if we can use this animation?

We can calculate the world position of where this animation 'wants' the ball to be at the ball touch keyframe. This is represented by the white ball in the images. We can calculate at what time that is going to happen (anim frame number where ball is touched * anim frame duration). We also know the predicted world ball position at this timestamp (see the ball section), this is the blue ball in the images. Now imagine a circle around the animation's ball position (green circle). If the predicted world ball position (blue ball) is within the circle's radius at the touch keyframe, the animation can be used.

What do we do with the difference in position between the predicted ball position and the animation's desired ball position? We could just move the ball to the desired position, but this will look jumpy (the famous 'sticky ball' from some older games). Instead, I prefer to move the player instead - let's call it 'slide'. The blue arrows denotes the slide translation that we need to add to the player position to make it align with the world ball position at ball touch frame. This slide is executed during the frames of the dribble animation before the ball touch keyframe. We now end up perfectly aligned when touching the ball. In this example image, the ball is too far away; the amount of slide needed is outside of our allowed slide circle. So, the animation is rejected. We need more movement animations to get closer to the ball first, or try another dribble animation.

It's important to notice that, by sliding into the direction the player is already moving, the player effectively 'cheats' by moving faster than the animation intended. This means if the player runs at top speed, it can go even faster by the added slide. But also during slower dribbling this is undesired, as the player will 'plop' towards the ball before each ball touch, which hampers fluency. This can be fixed by deforming the allowed area; compress the forward (in relation to player movement) part. At top speed, the whole forward part could be cut off so we never go faster than some maximum velocity.

Some tricks:

Another trick to make the animation cover more ball touch area is to automatically add extra potential ball touch keyframes before and after the one in the animation. In Gameplay Football, it works like this: first check what part of the body the ball keyframe position is closest to, for example, the right foot. Then add extra keyframes in in the frames before and after this, at that limb's position + the same offset as the actual keyframe. Now when checking if the animation is close enough to the ball, we have a lot more area to cover. This works especially well for anims that control incoming balls, as they often move in a very different direction than the player, so by adding extra frames temporally, we have a lot more positions on the ball's path that can be 'caught' by the animation.

A note on Inverse Kinematics (IK)

This is the holy grail of animating a humanoid. I've dreamed about this a lot during the Gameplay Football development, but it was unattainable at the time. No idea what the state of things is these days - it may be feasible for movement animations, though I think it's still going to be hard to actually control the ball in the process. One thing that I did do in GF (and modern FIFA/PES do this as well) is rotating the legs a bit as to touch the ball properly, when the anim's ball touch position and the actual ball position are a bit off. This can be used to minimize the amount of player sliding by having just the leg rotate towards the ball position, but will look funny when overdone.

Useful calculations

Time needed to get to ball

We need to know, for each player, the time needed for them to get to the ball. That way, we can decide who, and therefore what team, is most likely to get ball possession. This isn't as trivial as it sounds. Both the player and the ball are moving, so we can't just use the distance between player & ball as a guesstimate. I've been using an iterative approach, where I simulate a growing circle of potential ball interaction around the player over time, until the predicted ball position at some timestep is within this circle.

We can divide this simulation loop into 2 phases.

  1. In the first phase, our area of potential ball interaction (green circle) follows our player's momentum, since we can't suddenly run towards the ball without changing direction. Its radius is not growing, but its position is moving the same direction and velocity as the player is going right now.
  2. In the second phase, we could be running towards any direction we want at full velocity. This is simulated by having the circle grow in radius by our player's potential max velocity, with the center staying at a static position.

The key trick here is that these phases transition fluently from the first to the second. This transition starts right away, and after a certain amount of simulated time, we end up staying 100% in the second phase and continue until the ball is in the circle. This timestamp is the value we sought. The more agile our player is, the faster we transition from phase 1 to phase 2.

Designated player/team possession

The 2 team objects could both have a pointer to their player that is most likely to get to the ball soonest (from all the players in that team only). This is the designated team possession player.

The match class could have a pointer to the TEAM that is most likely to get to the ball soonest. This is the team whose possession player has the lowest time to ball. This is the designated possession team, as they are most likely to have/get ball possession.

The match class could also have a pointer to the PLAYER that is most likely to get to the ball soonest. This is simply the designated team possession player, from the designated possession team.

Player/team possession balance

Now we know which player is most likely to get to the ball first, we also know that their side can be considered the attacking team, while the other can be designated the defending team. However, real life isn't that binary. For example, when 2 players are both similarly close to the ball, it isn't quite clear yet who will end up in ball possession. Therefore, it's wise to not just have a boolean designation, but also a possession balance, that is < 0 when a player is less likely, and > 0 when they are more likely to get to the ball before the closest opponent. The tighter the battle for the ball, the closer to 0 this value becomes. If one of the dueling players will get to the ball in 0.5 seconds, and the other in 0.54 seconds, the one will get a value of 0.04, and the other ends up at -0.04.

thisPlayer.possessionBalance =
  otherTeam.designatedPossessionPlayer.timeToBall - thisPlayer.timeToBall

Note: In GF and Ballsy!, I used to divide the numbers instead of subtracting, ending up with a ratio (that I erroneously named possessionFactor). In hindsight, I think just subtracting the times makes more sense, since this will lead to similar differences in value when the players are further away from the ball, but similarly close to each other, while dividing will make the number get closer to 1 in that case, without having any logical reason to do so, other than that you could argue 'further from the ball means it's more unclear who will win'. So there's some logic to it, but I advise to try the subtraction method first. A hybrid can also be considered.

Just like with the team possession boolean, the team can also have a possession balance variable.

thisTeam.possessionBalance =
  otherTeam.designatedPossessionPlayer.timeToBall - thisTeam.designatedPossessionPlayer.timeToBall
Laggy possession balance

The team possession balance tells us if our team is attacking or defending. This influences AI player positioning. But the balance can vary wildly when 2 players are dueling for the ball. This could result in players quickly switching between running forwards and backwards. To overcome this issue, we can have a version of the balance variable(s) that is smoothed out over time.

laggyPossessionBalance =
  Lerp(laggyPossessionBalance, possessionBalance, 1 - Mathf.Exp(-changeSpeed * timeStep))

Players may respond differently to the resulting number based on their role. For example, a defender might want to move to a more defensive position when the laggy possession balance goes below 0.5, while a striker might only move back below -0.5. In a practical example, if you send a risky through pass to your striker, that 'might' get intercepted by an opponent, the striker will still run forwards to go for it, despite the possession balance being below 0 if the opponent is expected to intercept it. When the opponent fails to control the ball, the striker receives the ball after all, instead of having ran back to defend prematurely. And as an example the other way around; when there's a midfield duel going on that is 50/50, our defenders may already start man-marking their assigned opponents, so when the duel is indeed lost and the winner passes the ball to their strikers, our defenders are in the right place to defend.

Ball desire

The common mechanic in football games is to have the designated possession player be automatically moving towards the ball. When they are close enough, they interact with the ball, for example pushing it in the controller's desired direction when dribbling. Then, they automatically move towards the ball again. I will call this the ball desire, and it basically means if, or how much, the player automatically moves towards the ball (or rather, the future ball position).

In case you are using the Gameplay Football source as reference, I named this concept the 'ball magnet', but that may be confusing, since it's the ball that is attracting the player, not the other way around.

When you are the match designated possession player, this ball desire is pretty absolute; you will go for it no matter what (unless you press some super-cancel button combo, of course). When you are the designated team possession player, but not the match designated possession player, you might get a little bit of tug towards the ball, based on possession balance; for example, when you are dueling for the ball, you might want to guide the player a little towards the ball. Or when the controller presses a button (the pass button in most games) when not on the ball, the player may automatically move towards the ball, either fully, or as a middle ground between controller input and ball position, helping them with positioning towards trying to steal the ball from the opponent.

What ball position, exactly?

So what does that mean, moving towards the ball? If we want to get to the ball as soon as possible, we can simply move towards the predicted ball position at the timestamp we got from the 'time needed to get to ball' calculation. However, in some cases, we might want to aim for a later ball position. Let's look at the image on the right. We are the designated possession player, with no opponents around, and so we have a 100% ball desire. The left green X denotes the position that we should move towards to intercept the ball as soon as possible. But if the controller is aiming towards the right, maybe we'd prefer moving more slowly to the right green X and intercept the ball there. We would intercept the ball a bit later, BUT we will already be moving into the right direction, while in the first case, we would have to change direction if we want to move to the right after intercepting the ball, which may end up taking more time in total.

There may be other factors than our controller input to take into account; for example, if an opponent is near, we might prefer picking the earlier interception despite a conflicting controller direction, lest our opponent intercept the ball before we do. Also, we might want to take our current movement into account. Or we may prefer 'meeting in the middle' - moving perpendicular (90s degrees) in relation to the ball movement direction.

There's a few ways to go about all this; we can offer 2 or 3 optional paths and pick one using some if-then structure, or we can have a weighting system. The latter iterates over the future period starting from the minimum time we need to get to the ball, to some sane maximum, and for each of these options gives the various aspects a rating. The earlier options will have a high 'time' rating, the option whose direction is closest to our controller direction will have a high 'controller' rating, etc; and then we multiply these ratings with weightings that we arbitrarily give these ratings. For example;

for (timestamp = player.timeToBall; timestamp < maxTime; timestamp += step) {
  ..calculate ratings..;
  totalRating = timeRating * timeWeight + controllerRating * controllerWeight + ..etc..;
  if (totalRating > bestTotalRating) {
    bestTotalRating = totalRating;
    targetPosition = ball.GetPredictedPosition(timestamp);
  }
}

We can then play around testing various weightings to see which one performs best. Full disclosure: for both Gameplay Football and Ballsy!, I found it difficult to come up with a satisfactory setup. Having the system choose to run towards an incoming ball to minimize time rating, or even just standing still while a ball comes at the player at a serious velocity, feels frustrating when you actually want to end up running in the direction the ball is already going. So lower the time weighting? Well, that may cause the player to run 'in front of the ball', only to slowly decelerate as the ball is also decelerating in the process, ending up both standing still after all, and losing a lot of time in the process.

Ball control

Apart from the variables about designated ball possession, players may have a boolean for ball control, which basically means the player has the ball conveniently in front or just below them, rolling at a similar direction and speed as the player movement - so, 'dribble mode'. If a player has ball control, we can assume them to be able to touch the ball at will shortly, and we can omit the whole 'preferred ball position' routine in the paragraph above, instead just going directly towards the ball at the predicted position at player.timeToBall.

To check if a player is in ball control, just calculate if their movement is somewhat similar to the ball movement, if the ball position is somewhat close to the player position, if the timeToBall is within some threshold, and maybe add some weightings or other tricks to taste. I prefer to have only 1 player per team be in ball control at most, so we don't have 2 team players sticking to the ball, trying to dribble, so I also add a check if the player is the team designated possession player - else, no ball control for you! One could also consider only to allow the match designated possession player to have ball control, which may affect what happens gameplay-wise when a shoulder-to-shoulder duel for the ball is going on.

General positioning

We can break AI positioning up in two stages; first, the macro positioning which will be discussed in this chapter. This is the general base position for the player. Based off that position, we can do offensive and defensive micromanagement, which will be discussed in later chapters. The way I see it - but this is totally arbitrary - the macro position is part of the team class, which can be interpreted as how the 'coach' wants the team to position itself. This could be altered through sliders in the team tactical setup menu. The micro positioning is part of the player AI controller class, and can be interpreted as how the individual player behaves - so maybe a defensive player will do more man-marking, and a winger makes more runs into space.

Again, this is arbitrary, and there can be some overlap in responsibility. As a hypothetical, what would happen if you put Lionel Messi in a center-back position (besides him being very unhappy about it)? He's probably position himself roughly in the correct location, but would fail at man-marking, and he'd possibly make some forward runs at highly undesirable moments. Conversely, put Giorgio Chiellini in as an attacking midfielder, and he'd probably fail to make those runs, taking a more cautious approach.

Adaptive formation position

In soccer, players usually have a certain role and formation position. For this document, let's assume a special form of soccer with 8 players in a 3-3-2 formation. We'll ignore the keeper for the sake of clarity. The first 3 are the left, center, and right-back, forming a defensive line. The midfield has a similar setup, and up front we add 2 strikers.

Managers decide the depth and width of their team; this can be imagined as the formation being compressed into a rectangle of the manager's desired scale. The resulting rectangle then moves about the pitch, based on various factors, like the ball position, designated match player's position, and whether the team is in possession of the ball, or defending.

This can be implemented by having a focus-position variable that can go from (-1, -1) to (1, 1), which coincides with the formation rectangle moving from left-back to right-front, but never beyond the edges of the pitch. Both this focus-position and the scale of the formation rectangle can be altered as a means of implementing team style, for example pushing the rectangle up the pitch more for a high-pressing setup, of having it stay back more for a counter attack gameplan. We can move the focus-position based more on whether our team is in possession, as to fall back more when the opponent team gains ball control. We can compress the rectangle along the depth axis a bit when defending, like most teams tend to do, or just keep our width and depth, to trade defensive strength for having better positioning when we do regain ball possession.

We could add some dynamics within the formation rectangle as well, for example by having the midfielders move forwards and backwards a bit more than the defenders and attackers. This way, we can have a more crowded defensive area on defense, or a more crowded offense on attack.

The same can be done vertically, having the center-back, center-midfielder, and other more centered players move to the side a little to follow the action. This could be considered treading into the area of micro-management, and better suited to be acted out by the individual player's man-marking or offensive strategies, but there's an arbitrary overlap between micro- and macro-management anyway. I tend to go pretty far in this regard, for example, having the closest players be drawn to the ball in this stage already. The way I see this is 'this is something you want any player to do regardless of individual traits', but maybe that's just a poor excuse for an illogical setup ;)

Dynamic formation distribution using the Hungarian algorithm

We could assign our players a static formation position (left-back, center midfielder, etc.) to start with. But if the left-back receives the ball and decides to make a forward run, they leave the left-back space wide open. It would be nice if some other player temporarily covers this gap. So we want a dynamic formation assignment that can have players swap positions on the go (and maybe swap back when the action is happening far away).

The Hungarian algorithm does a great job in redistributing the current positioning situation into our formation mold. [I'm not mathematically inclined enough to be able to explain how it works internally]. If you happen to use C#, I can recommend Vivet's library, but the algo is common enough that you'll be able to find a library for your language of choice.

So how does it work? We set up a so-called 'cost matrix', a 2-dimensional array that contains the distances between all players' current positions, vs. all static formation positions. Toss it into the magical Hungarian algorithm, and it will come up with the optimal distribution that minimizes total distance. Our adventurous left-back may have passed the left midfielder, in which case the latter will probably be assigned the left-back position for the time being.

A potential problem is that the Hungarian algorithm tries to minimize total distance cost, without regard for any individual distance. In other words, when everyone is very close to a formation position, except for one player, who is on the other side of pitch from the open position, it will happily make the player walk for miles. Ideally however, we might want to have multiple players shift a position to accommodate a closer spot for the adventurous player. After all, this will minimize the time to have everyone settle in the formation mold, which is often more important than smallest total distance.

We can trick the algo into doing this by raising the distance costs by a power of 2 (or thereabouts) so that greater distances are non-linearly punished. Twice the distance? Four times the cost.

int[,] costMatrix = new int[formation.Count, formation.Count];
for (int f1 = 0; f1 < formation.Count; f1++) {
  for (int f2 = 0; f2 < formation.Count; f2++) {
    Vector2 actualPosition = [current world position of player belonging to formation spot f1]
    Vector2 formationPosition = [idealized world position for formation spot f2]
    // penalty for larger distance (and * 10 because Vivet's library wants integers)
    costMatrix[f1, f2] = (int)(pow((actualPosition - formationPosition).magnitude, 2.0f) * 10.0f);
  }
}
int[] assignmentMatrix = HungarianAlgorithm.FindAssignments(costMatrix);

Now we have the optimal dynamic formation indices for each base formation index in the assignmentMatrix, and we can tell the players that they should behave accordingly.

Mixing offensive and defensive micro-positioning

We now have a general position for the player, but we want attacking players to make runs into space, and defensive players to man-mark. We could have a binary switch based on the team possession, but it may look nicer to have a gradual mix. See also, the laggy possession balance paragraph in the useful calculations chapter. One could assign a constant 'defensiveness' factor to players based on their role, going from 0.8 for a center-back to 0.2 for a striker, and then combine it with the current laggy team possession balance, so that the CB defensiveness goes from 1.0 on defense to 0.6 when their team has ball control, while the striker goes from 0.4 defensiveness on defense to 0.0 on team possession.

In practice, this means that when a team has ball possession, their defenders may position themselves offensively somewhat, but will always mix in 60% of their ideal defensive position, so they stay close to their man-marking target. And because of the gradual quality of the laggy team possession balance, they will smoothly switch between the two.

Without the smoothness of the possession balance, the players will jitter back and forth when the ball is contended in a 1-on-1 duel, or on a doubtful pass whose receiver is yet unclear. In the worst case scenario, a brilliant through pass to our striker may pass so close in front of an opponent that our code assumes the opponent will be able to intercept the ball, which would make the team possession balance go the other way, which would make our target receiver immediately cancel their run. When the opponent turns out to miss the ball or only touch it slightly, the ball arrives at the intended position, but our strikes has already started moving back. This is why having the possession balance smoothed out a bit is important for fluent gameplay.

Defensive positioning

Man-marking assignment using the Hungarian algorithm

In defensive positioning, it's of great help for defenders to have a specific opponent to mark. Luckily for us, the Hungarian algorithm comes to our aid once more. This time, instead of inserting the distances between our current and our formation positions in the cost matrix, we insert the distances between our current position and the opponent's current positions. That's basically all there is to it.

We could consider tweaking the costs a bit to make sure the most dangerous opponents (the one with the ball, or the one closest to our goal) are defended by the optimal players. We could also take into account if the opponent is already closer to our goal than some potential assignee, in which case their distance may be small, but effectively, the assignee is already lost. In Ballsy!, I don't use these tweaks and it still works fine, partly because my defenders always take some man-marking into account when positioning, even while on the offense; so they rarely lose complete track of their target.

Offensive positioning

I'm going to be frank: I'm not too happy with my own offensive solutions so far. For my next soccer game, I will come up with a new system and may update this document consequently. The system I use for Ballsy! does work, but it's not good enough. I guess I've always been a more defense-oriented person. When it comes to soccer, that is, of course.

One part of the Ballsy! offensive positioning system isn't so bad; it's a weighting system (there's a bunch of those in my games, they really are just that good. After all, humans do the same thing subconsciously all the time, so it does make sense), that picks between a few arbitrary positional options for a player that isn't on the ball, when their team is in possession:

These options amount to positions, which are then weighted;

We can alter the weightings based on team style and player role. We can now decide what position is preferable for attacking players. However, the question that I didn't manage to answer is when to invoke this system. I ended up using an interval for each player, set to 1.6 seconds (based on experimenting what felt best). So this system just repeats every 1.6 seconds, and the weightings are done again based on a mixture of the then current player position, and their adapted formation position (so players will be able to progressively move further away from their adapted position, but will move slower once they get further away).

Using an interval works, but doesn't make much sense. This is not how real soccer players do their offensive positioning. Maybe a solution would be to set a very short interval, effectively having players constantly reconsider their strategy, with an extra weighting to prefer their previous choice, as to not have them continuously switch decisions, which would make them run around as if they were panicking. Anyway, I haven't tested this solution.

Either way, real life players probably make a lot more considerations, especially in the timing department. A striker will time their runs based on when they think the possession player will be able to pass, and will pick their positioning in a way that puts their defender on the wrong footing. For a deeper simulation, these things should probably be considered.

On the ball decisionmaking

Guess what; this section is going to feature another weighting system! Contrary to the one for offensive positioning, I'm very happy with how well this one turned out in Ballsy!. The AI seems pretty convincing on the ball.



BELOW THIS LINE == TODO