[icon ] blenderdumbass . org [icon scene] Articles

Making Breakable Cars in Video Games

[avatar]  Blender Dumbass

January 21, 2025

👁 146

https://blenderdumbass.org/ : 👁 7
https://primal.net/ : 👁 1
https://lm.madiator.cloud/ : 👁 2
https://mastodon.social/ : 👁 2

#DanisRace #MoriasRace #Game #Gamedev #UPBGE #blender3d #animation #GTAClone #programming #project #cars #damage #Gnu #Linux #Freesoftware #OpenSource

License:
Creative Commons Attribution Share-Alike
Not AI Generated! [icon internet] See Proof
Audio Version





Since Dani's Race Version 2024-09-25 is released. And the petition for Version 2025-01-19 has begun ( please sign it ), I thought it would be fitting to talk about a very big aspect of the game. Mainly breakable cars.

We all love some mayhem when it comes to playing games. And nothing makes car games more satisfying than damage models. RockStar Games understood it early on, and all GTA games have breakable cars. Today some of the most popular car games like BeamNG.drive holding on a realism of damage models almost solely. And therefor for me, making Dani's Race any other way, would have not been a good idea. I knew I had to make the cars in my game breakable.

So how exactly am I doing it?





The right approach


A big part of my early childhood were the extremely brutal R-rated GTA games, which I have a lot of nostalgia for. Those did not go full on deformation simulation that something like Rigs-Of-Rods, or BeamNG would do. The way cars broke, in those GTA games I have so many fun memories with, was much simpler. And I tried replicating this same approach in Dani's Race.


[embedded image]


The car model is actually multiple models put together. One for the body, one for the hood, one for each door. Those models can be replaced with a broken version, or completely detached. Simulating various stages in the car's health. As you can see on the screenshot there, this is exactly what I did for Dani's Race.





Coding the approach


One thing is to prepare models for a car, the other is to make them actually work in the game. The main problem is how do you know which model is being broken and when?

Thankfully UPBGE had me covered. The Vehicle.py file contains a function called OnCollision() which I could set to be executed on every collision in the following way:

def OnCollisionDo(obj, point, normal, points):

	if car["active"] or (InView(car) and Opt.GoodFPS("Collision"),0.6):
	
		OnCollision(car, obj, point, normal, points)
		
car.collisionCallbacks.append(OnCollisionDo)


Yeah, I stuffed the execution of the function into a wrapper that checks whether the FPS is good or not. The engine requires some measures sometimes to tame it.

But anyway, you can see that the function receives ( from the engine ) the following data: The car, or the object that is colliding. The obj or the object the car is colliding with. The point, or the position of where the collision happens. The normal, or the direction of the collision. And the points. With the "s". Because sometimes during the same frame there could be more than one collision happening in the same time.

The thing is, those point*s contain the crucial part of the information I was looking for when making the breakable cars.

for point in list(points)[:2]:
	force    = point.appliedImpulse / car.mass / 2


They have the appliedImpulse which is the push the car gets from the collision. It lets me know how hard was the hit. Because trying to estimate it ( which I tried previously using velocity ) wasn't going to work particularly well.

So using all this stuff I know during every collision how hard it is and where it happens. So I can, for example, find the closest object to the collision point on the surface of the car, like the door that just happened to be one that the other car just rammed into, and combine that info into something that will make that door deform.

But it is not all. The doors need to have their own physics.

def SwingDoors(car):

    # Doors and other hinged objects might
    # be swang around by the momentum of
    # the car.   
    
    reference = ( car["localLinearForces"] + car.localLinearVelocity ) / 2
    
    
    for door in car.get("doors",[]):

        # If locked, skip
        if door.get("locked", True) or not door.visible:
            continue
        
        # Getting door data
        minrot = door.get("minrot", 0)
        maxrot = door.get("maxrot", math.pi/2)
        lockat = door.get("lockat", 0)
        breakat= door.get("breakat", math.pi/2)
        axis   = door.get("axis", 2)
        factor = door.get("factor", 1.0)
        offset = door.get("offset", math.pi)

        # Rotating door  
        rot = door.localOrientation.to_euler()
        rot[axis] = (math.atan2(*(i for n, i in enumerate(reference) if n != axis )) + offset ) * factor
        for n in range(3):
            if n != axis:
                rot[n] = 0

        # Gravity assisted autolocking.
        # For hoods and things like that.
        # Where not moving means that gravity pushes
        # the door down.
        
        if door.get("gravity", axis != 2):

            if axis == 1: gravityaxis = 2
            else        : gravityaxis = 1
            
            
            gravityfactor = reference[gravityaxis]
            if gravityfactor < 0: gravityfactor *= -1
            gravityfactor = min(30, gravityfactor)/30
            gravityfactor = 1 - gravityfactor
            
            rot[axis] = rot[axis]+((lockat - rot[axis])*gravityfactor)
            

        # Rotation clamping.
        maxrot    -= minrot
        rot[axis] -= minrot
        if rot[axis] < 0: rot[axis] += math.pi*2
        
        if   rot[axis] > (maxrot/2)+math.pi:
            rot[axis] = 0
        
            
        elif rot[axis] > maxrot:
            rot[axis] = maxrot
        
            
        rot[axis] += minrot
      
        # Damping the effect a little.
        oldrot = door.localOrientation.to_euler()[axis]
        diff = ( rot[axis] - oldrot )
        if   diff >  math.pi: diff -= math.pi*2
        elif diff < -math.pi: diff += math.pi*2
        rot[axis] = oldrot + ( diff / 5 )
        
        # Applying rotation.
        door.localOrientation = rot

        # Locking door
        if door["health"] > 0.5 and round(rot[axis],2) == round(lockat, 2):
            door["locked"] = True
            rot[axis] = lockat

        # Detaching the door
        if round(rot[axis],3) == round(breakat, 3):
            door["health"] -= 0.003

        if door["health"] < 0.1:

            door.visible = False
            
            newdoor = Reuse.Create(door.name, 500)
            if "Borked" in door:
                newdoor.replaceMesh(door["Borked"])
            newdoor.position = door.position
            newdoor.orientation = door.orientation
            newdoor.worldLinearVelocity = car.worldLinearVelocity / 1.5
            ColorPart(car, newdoor)
			
			


As you can see I wrote a whole essay worth of code just to make the various doors swing in the game. I tried originally using the bullet physics that comes with the engine to do the swinging. But the doors didn't behave. So instead I decided to write my own horrible approximation of what the doors should do. Which works most of the time. It is bit a silly, sometimes they over-react to very minor movements of the car. But for the most part this does the job.

Though as you can see, this function needs a lot of stuff from the door. From minrot to maxrot to things like lockat, breakat, factor, offset and so on and so forth. Which I need to manually provide for each door. And it doesn't help my sanity that the rotational math ( because doors swinging are done with rotating them along the hinges ) is done with radians. Don't get me wrong, I like radians. But sometimes looking at a half a pi and comparing it to pi times 2 makes me scratch my head.


[embedded image]


And we are not even talking about the whole other complexity outlined in the Tools.py when it comes to fixing those parts. Because unlike in other games, where you just put the car in the garage and the car in magically fixed, in Dani's Race you need to fix it yourself. Part by part.


[embedded image]






Putting the car together for the game


Now because I know what I'm trying to do when making cars for this game, let's actually go over a step by step process of how I take a car from Moria's Race and make it a car in Dani's Race.

As you probably can tell, I can't simply use the models directly from the film in the game-sequel. The 2025-01-19 version summary shows exactly why. The versions used for the film are too high resolution. And it will not run well in the game. So the first step is to delete the resolution.


[embedded image]


Sometimes, like with Neonspeedster I'm lucky to have the pre-baked version of the car, from which a proper low-res version could be generated. Other times I need to manually delete unnecessary loops as you can see me doing in this screenshot.

It is a screenshot from a livestream I did, in which I added the Underground car from the movie. Here is the full recording of it:





As you can see I meticulously dissolved edges that aren't needed. Until I reduced the 150k, or so, triangles of a model to be below 5K triangles, while keeping the shape largely the same.

Then I struggled with rigging it onto the template I had from another car in the game. And setting up all the doors and stuff for breakness. The stream is not full. I did the deformation of the doors off stream. But you can see that a single transformation of a car. Not even making of an original car for a game. But using a pre-existent model, took me above 4 hours to put into Dani's Race. And it was so fast only because I know how to do it. The game has not a small amount of breakable cars by now.

I will remind you again. The version with this car and the new optimization methods will be released only when the petition is over. So please go sign it now and...

Happy Hacking!!!

[icon question] Help





Subscribe RSS
[icon link] Author
[icon link] Website
Share on Mastodon


[icon question] Help











[icon articles]The Psychopathy Epidemic

  Unread  


[avatar]  Blender Dumbass

👁 62 💬 0



Psychopathy - a lack of empathy, a lack of remorse. A fascinating topic. Not so long ago an Ultimate Hacker @Troler sent me a video about psychopathy. A normal video basically just explaining what it is. I thought it would bore me. I know what it is. But instead it made me think.


[icon petitions]Release: Dani's Race v2025-01-19

  Unread  

[thumbnail]


240 / 250 Signatures

[avatar]  Blender Dumbass

👁 102 💬 0



Dani's Race version 2025-01-19


#DanisRace #MoriasRace #Game #UPBGE #blender3d #project #petition #release


[icon software]FastLBRY

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 19 💬 0



A lightweight LBRY client.


[icon reviews]Leon The Professional

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 31 💬 0



There are a couple of movies that are so dear to me that I keep watching the end credits all the way through. Often crying through them. And Leon: The Professional is one of those movies.


[icon articles]The Challenge of Writing NPC Characters for Games

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 123 💬 1



I'm developing a game called Dani's Race, which is supposed to be a GTA clone. A game where you can run around a city and cause all kinds of mayhem. And a humongous part of the experience of such a sandbox world simulator are the reactions from the in-game non-playable characters. If you steal a car, what will be the reaction of the driver? What will the police in the game do? What will other drivers do if you hit them on the road? All of this is a part of my daily problem-solving when working on Dani's Race.


#DanisRace #MoriasRace #GameDev #FreeSoftware #Gnu #Linux #OpenSource #GtaClone #Programming #UPBGE #blender3d


[icon codeberg] Powered with BDServer [icon analytics] Analytics [icon mastodon] Mastodon [icon peertube] PeerTube [icon element] Matrix
[icon user] Login