I'm reading the GTA Vice City code that was decompiled by a group of people for fun (available publicly at github.com/halpz/re3). Minimal code was added merely to fix the bugs, so it's really authentic. The code base is really huge and fun to wander in.
I'm going to share my design and architecture analysis on the code base by asking questions about each episode's subject and providing answers in a way that you don't need to read the whole code.
This episode is about vehicles that construct a significant part of the gameplay experience, therefore this article is HUGE and took a lot of time to write. The goal is to share some ideas that can be useful for you when you design software, not to share every detail.
Let's start off this episode by answering the question I had in mind since I was 8-9 years old:
1- Does the game intentionally spawn similar cars to the one you drive?
Turns out, nope.
First of all, the concept of LOD (level of detail) is used in the game. Basically, the area that isn't in your certain vicinity, doesn't have any functioning NPCs. LOD is a way of structuring and optimizing the whole game and involves a lot of things in different layers of the software. For example, the models' textures in the distance are rendered with a decreased quality for optimization purposes, or the sound effects of distant events aren't played, just to mention two use cases. We love LOD. Depending on the game, LOD's usage and indications change; for example in the Sims 3, the agents that aren't near you are still "living," but the resolution of their actions may vary. For example, they may only do actions regarding satisfying their basic needs like hunger, rather than doing minor activities like viewing a statue (in the game, it's much more complicated than that) but in GTA Vice City, there isn’t a karma system or anything, so NPCs are stateless and can die/spawn in the spot.
Getting back to the subject of spawning cars, less than 30 vehicles are alive at one moment, and only around you. In trajectories and directions based calculations on the player position (whether it should be behind you, in front of you, etc.)
Note that police vehicles that chase you when you have wanted levels above 0 are amongst those 30. If you don't have a wanted level, cop cars are spawned with a threshold. So as gang cars. Both of which depend on the zone that the player is at. (Bonus: the number 30, is an rvalue, just a hardcoded number, in a for loop. Not a [const] variable.)
For generating a new non-special car, game mainly takes the following two concepts into consideration:
- Player: speed, wanted level
- Zone: time of day, zone population density, gang type for the zone (correlates with a car model), etc
(It takes many more parameters, but these are the gist of it)
In the case of cars, there are 9 "ratings" for how luxurious they are. Same as for boats and other types of vehicles, but with different values. There is a car frequency array holding information about the number of spawned cars within each rating class. Through a very dirty sequence of calculations, a car model is chosen logically (it doesn't have anything to do with 3d models yet)
The player's current car type isn't used when the game is spawning new cars. However, some of the other properties of the player's car, such as speed, are used to calculate the transform properties of the newly spawning car.
I would say this part of the code isn't that educational and inspiring from a software design and architecture standpoint. The main things to take-away would be:
- using LOD
- using map zones to introduce variety in NPC behavior/presentation
- using frequency as constraint parameter
The code for this part was not clean to say the least. It doesn’t mean anything other than the fact that at that time, late 90s - early 2000s, they developed games without the vision of constantly having to ship updates afterwards. The whole development model was based around heavy-weight, waterfall processes; not the agile, iterative, constantly-changing-requirement development models that are common today. And it’s actually the better choice for this type of project. I will write up my ideas further around this subject later.
Maybe the reason for me feeling this way was that there aren't a lot of car models in the game.
2- How do cars that are out of sight get removed?
So this is a follow up to the LOD concept in the previous question and it’s about removing useless things out of sight.
There is a "vehicle pool" which is a singleton container that has pointers to all living vehicle objects. Amongst the pool, the objects are checked and removed mainly based on how distant they are to the player.
The further question is, where and how frequent is the said function called? It’s called in the main game loop. Every time the loop iterates, this method is called and cars are cleaned up. You could say that this method is the heartbeat of the whole game. The iteration frequency is around hundreds per second (it’s more complicated than that, but you get the point.)
Game loop is one of the fundamental concepts, both in game design and game engine software architecture. In game design, it’s one of the fundamental questions you have to answer when coming up with the idea of your gameplay. In software, this pattern, in the most naive sense, involves structuring the game code to a setup phase, a loop, and a shutdown phase. The loop can contain anything within the game flow: updating logic, getting joystick inputs, rendering stuff, updating the ui, etc.
The key ideas that can be useful and are worth looking further into are:
- object pool design pattern
- game loop pattern
3- How does a vehicle controller work? (physics + inputs)
General Structure
- Each type of vehicle has its own controller. All of them are children of a "vehicle." Vehicle itself is a physics object
- and a physics object is an entity. Being a child of an entity, enforces it to implement a method referred to as
ProcessControl
. This method is called by the world object as it's going through all of the alive entities in every frame of the game.
This level of abstraction hierarchy is good for the use case of a vehicle:
- Being a physical object gives it properties such as moving speed, mass, buoyancy, etc, along with respective methods.
- Being a vehicle gives it properties such as steering angle, gas pedal, brake pedal, horn/siren, gun firing time, gear, etc.
- Being a specific vehicle type mostly means using the properties and methods passed on by parents as well as adding to them, in concrete ways. For example, cars focus on specific calculations on their wheels because two wheels are connected to the engine and two are used mainly for steering, any of the four can be flat tires, etc. On the other hand, boats don’t have wheels, but they have specific calculations regarding buoyancy, helicopters for floating in the air, and so on.
For this question, I will talk about cars as an example, you can read about the other types such as helicopters in the code. The class for cars is called Automobiles.
Input Control
The Automobile class handles everything that you can possibly imagine about a car: inputs - gears - hydraulics - collision - horns - doors - damages - burst tyres - blowing up - horn sound - ...
For this question, let's just focus on inputs from the player through either the keyboard, or the joystick.
Being a child of the vehicle means that the class must implement a virtual method for handling the keyboard input. The input handler for a car does many things, and one of most important amongst them is steering to left and right.
For steering, the keyboard input means nothing on its own because it's just a float value between -1.0 and 1.0. It must be converted to an actual radian degree value in order to be applied to the actual car's rotation amount. The new amount isn’t replaced by the old amount, but it’s done through clamping which ensures a smoother transition and is used a lot in games.
Note that, the result of all of the calculations within this method is just assigning some values to some variables such as m_fSteerAngle
and nothing more than that.
The actual place that the variables are used is in the process control method that was mentioned previously. The reason is that the user’s input isn’t the only thing that affects a vehicle’s properties; a lot of other non-input stuff can cause change for those variables, such as having a flat tyre. In the end the process control method is updating the car and doesn’t care how and why the variables’ values have changed, it just applies them.
A question/idea here would be, are the value modifier methods and the value applier methods working in the same thread? If you think about it, you can have these two sets of methods manipulated concurrently by using mutex and such. However in the game, these types of logics are handled in a single thread. It can be for many reasons, let me know and I can write more in some OVA episodes.
4- How does vehicle collision with world objects work?
The concept of collision detection within the game involves a lot of different types of physical entities within the game. I will have a separate episode for collisions in general.
5- How does the interaction with the car doors work?
First, there’s a design decision worth mentioning about the code. As mentioned previously there’s a chain of inheritance for the automobile (which is entity -> physical -> vehicle -> automobile) but the chain doesn’t continue to extend for the specific types of automobiles such as a bus, police car, taxi car, tank, etc. This means that all of the specialties of these types need to be handled within the same class. This can turn out to be messy if there are a lot of differences in the fundamental way each child class functions. For this case, the main differences aren’t that much, and are handled using if-elses and other measures. But for your own code, you have to think about the requirements at hand and consider a sweet spot for the amount of abstraction you create. This amount can always change as we work more on the code base and add more features to it. We just don’t want to over-engineer and we don’t want to under-engineer either.
Regarding doors, the automobile class considers 6 doors. Even though, for example, a Ferrari has 2 doors, and most cars have 4 doors. The reason comes back to the thing I mentioned in the previous paragraph.
What do we need to consider about a car door?
Is it missing? (like to use to measure how damaged the car is)
Is it open/closed?
Is it ready to be used? (like is the car stopped so that you can open it or jump out of it, etc)
Where is it located? (front/normal back/"special" back (e.g. ambulance))
It might seem obvious, but it’s worth mentioning that as for the answer of a lot of these questions, the class only needs to provide getters that are used by other classes (such as IsDoorOpen(DoorPlace)
. The car itself doesn’t use any of those properties unless it makes a difference on its own functionality. If not, it provides the information to other objects and it causes something in the game to change.
Within the automobile class, the doors are mainly used for the animations. There’s a method for handling open doors. This method doesn’t need to be constantly called, like the methods for input handling. So these methods are called from outside whenever the game logic knows that something happens to a door and wants the target car to handle the changes. For example the player class calls the method of a vehicle instance when it opens the door of that vehicle by sending a command to the command bus.
The door animations are dynamic, meaning that they are different based on vehicle models and different within the same doors in one model. So the code basically rotates the door part of the 3d model, with the hinge being the pivot point, in a certain duration.
Now, when a pedestrian is opening a door, we need to have animations for the door, but more importantly, the ped’s model has an animation too, and these need to blend together. I’ll talk about this and animations in general later.
A very important question to have a clear answer for is this: how is a specific car and/or door accessed from outside? Because I mostly talked about the class itself. There are a lot of living instances of that class. Answer: a ped has direct pointer reference to the vehicle object its driving as well as the knowledge to the door it has accessed the vehicle through (it’s an enum {front-left, back-left, etc}). Basically every interaction between objects/world is done either by events firing in the bus or direct pointer references.
6- How does the vehicle dynamic sound effects and radio work?
It was talked about in episode 1.
7- How does a car change colors?
Or more technically phrased: How does the car 3d model and textures properties dynamically change in the runtime?
This is applied to when a car is spawned or when a previously-spawned car wants to change colors (like in an spray shop)
When we only have one class for any automobile, how do we differentiate the 3d models of a Ferrari and a Chevrolet? Being an entity enforces children to call a parent method called “Set Model Index” indicating the model id. So it is used by many more things other than automobiles.
Let’s say you have a Ferrari car model that you want to change the color of. Knowing the model’s logical name within the code, you can use a class called VehicleModelInfo
, through which by indicating the model name, you can get access to and manipulate values specific to the model. When you pass the model name to VehichleModelInfo
, it searches the registry of all models properties and finds the exact model you’re looking for.
Under the hood, on the rendering side, it does have things to do with materials and shaders, but the game logic doesn’t know much and doesn’t care about that. VehicleModelInfo
and its parent, ModelInfo
, have a lot to do with rendering too. I’ll talk about rendering in a specific episode. But I think it’s useful to mention one last idea about it here for now:
You don’t want the rendering engine to have a lot to do with your logic. Conversely, you don’t want the logic to be concerned with rendering that much.
At the same time, sometimes you just can’t avoid completely isolating the two and having some rendering functionalities and settings on the logic side and vice versa. So, at least, we want to shove all the rendering-related settings of the logic layer to one or a few specific places and shove all the logic-related settings of the rendering layer to one or more specific sides. And we want to make these two shoved sections as painless to communicate as possible.
8- How is the car damage realized and handled?
For a car the damage can be applied to doors/hoods/lights/all of it (like when it blows up).
There’s a damage manager object inside the automobile class which basically sets the status of damage for each damageable part of the car. The parts include the engine, 6 wheels (not 4 as explained previously), lights, and panels (hoods). The status of a damage is an enum object indicating how severe it is. It also provides methods for modifying them. For example, applying differential damage to a certain part, resetting damage, applying max damage to all parts all at once, etc.
As for blowing up the vehicle as a whole, a non-pure virtual method exists in the base vehicle class. For an automobile, this method is implemented to:
- Apply max damage to every part
- Shoot the doors and wheels flying
- Shake the camera
- Kill the peds in the vehicle
- ...
9- How do you steal a car?
(FBI, I’m talking about the code of some game, not about real life. Don’t take it literally bro)
As far as a vehicle is concerned, it doesn’t matter if the car is stolen. A vehicle has a pointer to its owner and vice versa. If the vehicle’s owner isn’t the ped that just got into the vehicle, it means the car is stolen, and as a result, an event gets fired to the event bus. And after that, the ped will be assigned as the new owner of the car. Everything I mentioned is handled within the pedestrian code, not the vehicle.
10- How do special cars missions work? (like taxi and ambulance)
I will talk about it in a future episode. I call these car missions, as well as rampages, collectibles, etc, “open missions.”
NEXT EPISODE:
I did delete some of the questions I initially intended to include because it got too long. However, there will be more episodes. As you have seen, there are many topics that can be covered. Since this episode was rather heavy, the next will be smaller. The topic will be about crime handling and wanted levels.
You can comment your ideas and questions in general. I’ll be happy to talk about them!
I also have some ideas on having OVA episodes, in which the content will be about super niche or abstract subjects within the game. Let me know if you have suggestions.
Edit (Jul 26th, 2023)
The goal of this series was to share practical knowledge about software design with the help of a real-world project. Looking at the stats, the series doesn't have enough engagement yet after a few weeks of posting, maybe it's me, or maybe it's the subject matter. In any case, I take this as a failed pilot idea and discontinue the series. I will try to find other ways to spread the word about my passion which is software design and architecture in another form.
Edit (Mar 24, 2024)
Okay actually it has picked up traction. I will continue this series lol. But I'll work harder to design higher quality questions.
Top comments (1)
nderstanding the vehicle code is crucial for modders and game developers looking to enhance or modify the in-game driving experience. It's a reminder of the meticulous craftsmanship that went into creating the immersive world of GTA Vice City download for pc and sheds light on the underlying mechanics that have made it a classic in the open-world gaming genre.