Note: Mafia III is rated M for Mature 17+
Some media used in this article may contain violence, blood and gore, sexual content and/or strong language. Viewer discretion is advised.
Mafia III is an action-adventure video game set in the fictional city of New Bordeaux. The protagonist Lincoln Clay fights to take power away from the Italian mob. Mafia III: Developer-Narrated Gameplay: https://youtu.be/_Xtq18Vmh3s
Player can engage in an open world activity called Bounty Hunting.
It involves tracking down a target, immobilizing them, and bringing them to a location to collect a reward.
Here we will describe the main "transportation mechanism"; Bodies in Trunks.
Player can hide the body in a car trunk, drive to the destination, and remove the body.
The player has 2 main interactions for this feature:
Picking up a body.
Placing in a car or removing from a car.
Both are done via a system called Actor Actions:
1. An "action" is an instance of an "interact" that can be attached to a physical object in-game.
2. The action is either unusable or usable based on some conditions (proximity of player, game state, etc.).
3. The player (actor) can "execute" a usable action by pressing an input button.
Here is a video with some debug visualization of these interacts being in an unusable state (white), and then going to usable (green) state as the conditions are satisfied.
If the player walks empty-handed to a car, they cannot use the Trunk interact.
When player is within some range of the body, the "Pickup Body" interact is usable.
Now when they come close to the trunk, the "Put Body In Car Trunk" interact is usable.
When the body is in the car, the "Remove Body From Car Trunk" interact is usable.
When the interact is executed (i.e., button pressed), game starts playing animations, one on the player, and one on the body. ("body" refers to the NPC that the player is carrying).
These 2 animations are synchronized to look correct.
During the "placing in car" sequence, the body's animation is straightforward: it goes from player's shoulder to the car trunk.
The player's animation has a few sections to it, defined by events triggered at specific frames. When an event is detected by gameplay code, appropriate functionality is executed.
The animations are made assuming player is facing the right way and is at the right distance from the car trunk. i.e., they only look good if that alignment is correct.
But for gameplay we must give some leeway to the user. In the worst-case player can stand at the edge of the car, face away, and execute the interact.
The first few frames of the animation perform "docking"; i.e., align a specific bone of the player model to a specific bone of the car model. That involves quick rotation and sliding of the character.
In the video below, yellow & green arrows show the current orientation of player and body, and the blue sphere & lines shows the final position / orientation player must reach.
Open Door, Close Door
The car is not part of the player/body animations. It has independent functionality to open/close the doors.
So at the right moment in the player animation, we trigger an "open door" event, and similarly a "close door" event.
As a nice touch, we also tell the car to shake a bit after the animation finishes, as a reaction to the doors slamming.
If you noticed, the cars are different in the last 2 video clips.
The trunk doors open differently, so we have car specific player animations as well.
Mafia 3 has more than 45 different vehicles (normal cars, trucks, sports cars, etc.) and most are supported using a few variations of the main animation.
For cars that are too tiny to hold a body in it, we just disable this interaction.
Apart from the animation itself, the docking measurements are different per car too. e.g., the trunk may be deeper in a car, so player must reach in farther to place the body. We have docking offsets for some specific cars to make this look good in most situations.
It's configurable in data as an offset from the trunk interact position (the docking target position):
- TrunkDockOffsetX - Along width of the vehicle
- TrunkDockOffsetY - Along length of the vehicle.
Not all trunk spaces are big enough for the body. Sometimes the user would see clipping (part of the body sticking out through the car). To minimize this potential visual oddity and to avoid the render cost, we make the body invisible after it is placed in the trunk. But some cars have visible cargo space (e.g., rear doors with windows), so for them we do not hide the body.
Yet another special case was Flatbed Cars.
These do not have a typical trunk, so the interaction is slightly different. The code treats it like throwing on / picking up the body from the ground.
Additionally, if the flatbed car flips over, we detach the body from the car. Note that this detach is specific to flatbed car's only, since they have an exposed trunk.
All the above is handling the core feature involving the player, the body, and a car.
However, once we had these feature fundamentals in-game, we also had to account for various gameplay scenarios.
E.g., if a civilian was in the driver's seat, they could drive the car while the player is interacting with it.
That end-result was that the body would be floating in the air during the animation. And it was also odd since the body would be taken away by the unsuspecting driver.
To address that, we send an AI event to the driver, which makes them react by jumping out of the car.
Driving is not always smooth; cops could be chasing the player, player hits something, car overturns, etc.
When a car is turned over, we cannot play the full animation to remove body from the trunk; it was not authored to cover all the different angles a car could end up in due to game physics.
When non-flatbed cars flip over, we keep the body in the trunk. And, once the player interacts with the car trunk, we perform a camera cut and skip the part of the animation involving the car.
Additionally, we do not allow placing the body into the trunk of a flipped car.
We also handle damage to the car in a special way.
Usually after enough damage, the trunk gets crumpled, can open partially and flap as the car moves, or just fall off.
But if a body was inside, this would look weird (remember we usually hide the body), as well as cause problems in retrieving the body (the trunk door may not open properly if it's already partially open).
So we decided that the trunk would never open due to damage, if there's a body in it.
If a car is totally destroyed (E.g. blows up), we just fail the mission, so no need to worry about retrieving the body.
Lastly, if player leaves the car and goes far away, the mission script detects it and warns player of leaving the mission space. If player still goes farther, the mission is failed.
If there was no script logic, we still handle cleanup in code: the traffic system would despawn the car for performance, and thus the body would despawn too.
We had to handle these edge-cases so the user is never blocked from progression of a mission and can always retry/continue.
Even though this seemed like a simple feature in the beginning, we had to handle a lot of special scenarios that we could not have imagined without first trying it out.
Thanks to my colleagues at Hangar 13 for helping with this feature, which touched on many disparate systems.