There are two main aspects to the problem of handling collisions in a simulation...
Detecting the collision
Handling the consequences
The first of these is, essentially, a geometry problem.
The second is, to some extent, a physics problem.
Detection
At its simplest, detecting a collision within some group of objects requires us to calculate whether the volumes of any of those objects overlap in (virtual) space.
(More correctly, we might actually have to think about the extruded shapes of those volumes over time.)
Anyway, if we represent the physical "collision volume" of our objects in terms of geometric primitives such as triangles, circles (spheres), rectangles (cuboids), this becomes a solvable maths problem.
Handling
Handling a collision is more of a physics problem, potentially involving the calculation of collision-resolution impulses and so on --- or, at the very least, the "brute-force physics" of simply moving things out of the way of each other.
The details very much depend on how sophisticated a "physics system" your simulation has.
Up till now, we've just been doing things like negating the velocity of one of the moving objects, to implement a simple form of point-based solid body physics.
Context
But, above both of these aspects is the broader question of where/when/how to process collisions within the mainloop...
A Naive Approach
One way of addressing the whole problem is to allow the mainloop to proceed essentially as normal, updating the positions of each entity in turn.
Then, after all of that has been done, you make another sweep over the objects to detect and resolve any resulting collisions.
In a sense, this is quite an "honest" approach, and is consistent with the illusion that our entities are, in fact, moving simultaneously (rather than in any particular order).
Simultaneous
This strategy provides a nice, clean, separation of concerns and seems quite appealing on those grounds.
Detecting the collisions is no more difficult, and no less efficient, with this approach than it would be in any other.
And, in fact, during its early development, the original "Grand Theft Auto" (1997) worked this way.
The Problem
The problem is Collision Resolution (aka Collision Response).
If all of the objects have already moved to new, provisional, positions before you attempt to resolve the collisions, it becomes very difficult (in some cases) to figure out what to actually do when a collision is detected...
Collision Response
The main job of a collision response is to prevent an interpenetration from being seen ---
because we expect separate solid bodies not to overlap each other in space, rather by definition.
Ultimately, you do this by moving one or more of the colliding objects to some unoccupied region of space.
But where?
Collision Cascades
A very naive approach might just shift one (or all) of the intersecting objects apart by some sufficient distance to prevent them from overlapping...
...but what if that movement causes another collision, whether with a static obstruction (like a wall) or with some other dynamic entity?
In the former case, you have a big problem, because the wall certainly cannot move away in turn! Even in the latter case, the possibility of crazy knock-on effects is very real.
Revert
A more sensible approach is to revert a colliding object to its previous position.
But there is no guarantee that the previous position will still be unoccupied, because some other dynamic object might have moved into it!
In the overall scheme of things, such occurrences are relatively rare --- but they can happen and, in practice, they do happen... and when they do they are an enormous pain.
This is the trap that GTA fell into.
GTA Hell
It was possible to create a situation where the player could put a bunch of vehicles in a physical loop (nose-to-tail, in a loose circle as seen from above).
If, in this configuration, the vehicles all moved forward, and any one of them triggered a collision, the required "rollback" was essentially impossible to perform, and would result in an infinite loop!
(The full details are a bit more complicated, but that was the gist of it).
Purgatory?
The central problem was that, in the general case, you couldn't always guarantee the existence of a suitable "safe place" for an entity to return to.
However, there exists a very simple solution to this which, as far as I know, is how most games have worked since (especially the "sandbox" ones)...
Non-Simultaneous
The easiest way to guarantee the existence of a suitable "safe place" for an entity to return to, in the event of a collision, is to use its previous location.
...If we can guarantee that the previous location will not be occupied by anything else.
The way to do that, is to move and collision-resolve each entity in turn i.e. one at a time, as part of their individual update routines.
Wrong! (?)
Of course, strictly-speaking, this is "wrong" insofar as it contradicts the idea that our entities should be acting simultaneously.
In particular, the collision behaviour of two objects will now depend on the order in which they are updated.
e.g. consider a race between equals...
The apparent winner will be the one who gets updated first, which is technically arbitrary and "unfair".
..But..
...on the other hand, in practice "we get away with it", and it makes collision response much easier: if all else fails, we can always move an object back to its previous position, and be safe in the knowledge that this will work...
Right?
...erm, unless the environment itself is dynamic e.g. with moving platforms etc. (Such things are usually handled "specially", and I won't go into the details of those here).
Detection in Detail
As discussed at the start, collision detection is essentially a geometry problem: it's a matter of discovering the dynamic intersections within groups of geometric primitives e.g. points, lines, circles, rectangles, spheres, triangles, cuboids, cylinders and so on.
Each collidable entity is given an associated collision representation (sometimes called a "collision model" or "collision volume") which is used by the collision system.
...Approximately
These representations are almost always approximate, because the handling of perfectly exact representations is generally too slow and complex in real-time... and because the approximations are usually accurate enough for this to be a "fair trade-off".
In simple cases, an entity might be adequately described by a single primitive e.g. a box. However, more generally, an object's collision representation might include multiple primitives (potentially intersecting with each other!).
For Example
Bounding Volumes
As the number of primitives in a model increases, however, the time taken to compute the collisions increases too, which can lead to performance problems.
To address this an entity will typically have, in addition to any detailed collision model, a primitive outer model which is used to provide a very simple shell to support an "early rejection" optimisation.
Such outer models are often referred to as "bounding" models, typically "bounding boxes" or "bounding spheres".
For Example
2D Collision Detection
In 2D games, the collision primitives tend to be things like rectangles and, quite often, circles.
BTW: Occasionally, a 2D game will also perform "pixel perfect" collision using the actual sprite-data, but this is less common (and uses somewhat different techniques, which I won't cover here).
Circles
Circles are a popular choice for collision primitives because...
Well... they are easy to write collision-detection routines for!
This might seem like laziness (and, in a sense, it is), but an "easy" solution also tends to be a fast/concise/elegant one, and there is much to be said for that.
Code
Two circles are in collision if the distance between their centre points is less than the sum of their radii.
var distance = distanceBetween( c1.centre, c2.centre );
var limit = c1.radius + c2.radius;
if (distance < limit) { /* it's a collision! */ }
else { /* no collision */ }
Computing the distance between two points is easy, although it does involve taking a square-root which, historically, was a slow operation. There's a standard optimisation/work-around for this (although it's probably a less significant saving today than it used to be, but it's still widely used in practice)...
Squaring the Circle?
If we square both sides of the equation, the inequality will still hold:
var distanceSq = distanceSqBetween( c1.centre, c2.centre );
var limitSq = square(c1.radius + c2.radius);
if (distanceSq < limitSq) { /* it's a collision! */ }
else { /* no collision */ }
That is, we save ourselves the square-root operation, at the cost of performing an extra squaring operation on the "limit" value. That's generally going to be a "win" overall.
Rectangles
In 2D games, we are generally not too concerned with rotations (or, if we are, we use circles instead!), so we tend to treat rectangles in an "axis-aligned" form i.e. with perfectly horizontal and vertical edges, running parallel to the co-ordinate axes.
I would call these "axis-aligned rectangles", but you'll often hear the term "axis-aligned bounding-box" (commonly abbreviated to AABB) used for the same purpose, and for its 3D equivalent.
Code?
Anyway, axis-aligned 2D rectangle collision is a little bit more complex than circle collision, but not terribly so...
Let's start by thinking about it, and see if we can come up with an "algorithm" to solve this problem...
Indeed, we've been doing variants of that since PONG!
BTW: In some situations, contrary to what I claimed earlier, we might actually want to have rotatable rectangle collision...
...I won't go into the details of that here, but these are called "oriented bounding boxes" (OBBs)
Intermission
(This is roughly the half-way point)
Sufficient?
So, in principle, it might be enough to define box and/or circle models for each of your entities and then use those to detect any collisions. Right?
...well, not necessarily.
Thus far, we've just been thinking about "static" overlaps i.e. taking the desired position of an entity and checking it for collision against some other bunch of primitives.
Technically speaking, that isn't always sufficient...
Dynamic Collision
Unfortunately, if an object is moving fast enough it can apparently "warp" or "tunnel" through another object without our "static" collision detection spotting it!
This is generally known as the "Bullet Through Paper" problem, for hopefully obvious reasons...
Bullet Through Paper
The problem is that, in a dynamic system, we should really be testing against the "swept volume (or area) over time" of our entities.
Geometrically, these swept forms can get quite complicated, even for originally simple primitives. The linear sweep of a circle is a kind of mutant circle/rectangle hybrid, which is sometimes referred to as a "sausage" --- others call it a "capsule" or, generalising slightly, a "lozenge"
Sweeping Generalisations
The sweep of a rectangle is similar, until you start thinking about rotations, at which point it becomes utterly evil.
In some cases, these "swept volumes" can be used in a collision detector but, in general, they are quite impractical (especially when arbitrary rotations are involved).
As ever, the solution in the gaming world is to take some short-cuts and make some approximations...
Multi-Step Motion
Rather than computing a true swept volume, it is often sufficient to simply test against multiple instances of the original primitives, stepped over time in a way that compromises between reducing the "gaps" while keeping the number of steps reasonably modest.
The number of "steps" can be determined either by directly geometrical considerations (e.g. don't step by more than your own radius), or by temporal ones (e.g. don't step by more than 0.01 seconds, regardless of the overall "update rate").
Limits
Sometimes, objects are simply given maximum velocities which relate to their size and allow us to control the number of steps that way (often arranging things so that the number of steps per update can be "one").
In practice, for typical combinations of object size, velocity and update-rate, the problem is (as with so many things in this field!) essentially "ignored", albeit knowingly (and with some checks-and-balances to keep things sane).
Bullets Again
However, the motivating example of a bullet is often handled specially: bullets are an important feature in many games but they are unusually small and fast-moving... in a way that breaks many of the typical in-game assumptions.
Luckily, the solution here is quite easy --- the swept volume of a bullet is, to a very good approximation, simply a line!
The intersection of a line against most other primitives is easy to compute and, in addition to bullet collision, these facilities are used to help with visibility detection & object "picking".
3D Collision Detection
In 3D, the primitives are cuboids, spheres, convex hulls of various kinds and, sometimes, cylinders and triangles.
Cuboids and spheres are handled as natural extensions of their 2D equivalents (rectangles and circles).
And, although few objects in a typical game will seem obviously "spherical", the technique of using multiple spheres to approximate an arbitrary volume often works well, and can be very efficient (and simple).
Some Examples
We used this extensively in "Wild Metal Country", where almost all dynamic objects (including all of the "tanks") were represented as groups of spheres, packaged together inside a single overall bounding sphere.
In fact, the collision in "EVE Online" is also to this day, entirely sphere-based.
Sshhhh!!! Don't tell anyone.
It's embarrassing.
Triangles
Despite being the fundamental primitive of almost all 3D graphics, the direct use of triangles in collision detection isn't as common as you might think, because that level of fidelity tends to be overkill for most things (and a bit of a hassle complexity and efficiency-wise), but they are sometimes used to represent the static environment, especially terrains.
Convex hulls are often useful in 3D, and have numerous collision-friendly properties. The key fact about a convex hull (and this includes simpler things such as plain cuboids), is that a point is only "inside" the hull if (and only if) it is on the "inner" side of all of the hull's bounding planes (i.e. the planes which extend through the faces of its surface).
This turns out to be very quick and simple to calculate... so much so, in fact, that non-convex volumes in a game are usually converted into groups of convex volumes instead (this conversion is always possible).
Representing Humanoids
An important special case is humanoid characters. Several approaches are possible here and, indeed, a set of spheres would actually be OK for many things. However, in practice, it is common to represent characters as upright cylinders which, if you think about it, are just a simple extension (well, extrusion) of a circle into 3D space.
The fact that the cylinders remain vertically upright is an important simplification, which is generally applied, regardless of the angle of slope that a character may be standing on. (The Unreal Engine works this way).
Hierarchies
3D models are often represented hierarchically, with movable joints between the parts. This adds some complexity to the collision detection system, because the collision model itself may have to change when the entity's joints are modified.
When the collision primitives correspond directly to the segments of the hierarchy, this works fairly "naturally".
But, when a collision primitive covers multiple segments of the hierarchy (as the overall bounding volume surely does), there can be problems with maintaining its correctness.
Problems of Parenthood
It's possible for a "parent" volume to be recalculated so that it always fits the current position of the child nodes, but this is potentially expensive and, in my experience, is not often done.
Instead, the "solution" is to ensure that the bounding volumes are generous enough to contain all of their child volumes in any legitimate pose.
3D Environment Collision
Collision with the overall game environment (e.g. the terrain or corridors or buildings or whatever) is another important special case in many games...
For one thing, collisions involving non-environment entities are, in the broad scheme of things, "rare" (perhaps only occurring every couple of seconds => on less than 1% of all computed updates). But environment collisions are usually happening all the time, for almost every dynamic object.
Performance Matters...
Environment collision can be very performance-sensitive --- especially in vehicle-based games where, depending on how you handle it, each wheel is in a state of essentially constant (and complex) collision with the environment.
Furthermore, the essential geometry of an environment tends to be a bit different from that of a non-environment entity. Environments are much larger and usually have much more complicated collision geometry than other entities, and are more likely to be represented as "triangles" than as the simple spheres, boxes and cylinders used elsewhere.
Difficult
These factors make environment collision generally harder to implement, more computationally expensive, and more critical to overall performance.
So, naturally, we take shortcuts...
Height Maps
The way a "Height Map" works is to simply store a series of regularly-spaced height samples across a regular grid (i.e. an array)
This tends to make for a simple implementation, which is fast, memory-efficient, and quite suitable for many situations.
My own "Wild Metal Country" worked this manner (in 1999).
And it's what "Skyrim" does too.
Height Map Creation
You can create a height map by "any means necessary", including generating it as a fractal.
Or you can let the artists draw it directly as a grey-scale texture, or you can provide them with an actual editing tool which lets them loft and sculpt the environment in real-time.
We did all three.
Height Map Texturing
You then texture the terrain using a set of pre-rendered tiles which represent the various surface types available to you.
In a system with texture-blending, you can perform arbitrary overlapping blends to create smooth transitions between these surface types.
In WMC, we didn't have that option, so we had to use a palette of pre-blended textures and pick the "best fit" for each tile on the map, based on its supposed composition.
Wild Metal Country
I don't care what anyone says, I'm still proud of it.
How Skyrim Does It
Terrain Texture Examples
2D Environment Collision
Analogous techniques (and simplifications) are often applied to handling the environment in 2D games also.
In particular, the environment is often (though not always) built on a fixed grid, which can be conveniently represented as a 2D array, rather than as an arbitrary set of free-form rectangular blocks (or whatever it might be).
...Consider the background tiling of an old Mario game, for example.
In Conclusion
This concludes our introduction to the exciting world of collision detection... but there will be more next time.
OLD NEWS: I'll be taking my original set of "Computer Game Programming" lectures offline for a little while, but will be publishing them again (with some revisions), week-by-week, for the Autumn 2021 version of the course, starting on August 23rd.