[icon ] blenderdumbass . org [icon question] About

Making Breakable Cars in Video Games

[avatar]  Blender Dumbass

January 21, 2025

👁 85


#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!!!





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












[icon forum]How Can I Improve The Website

  Unread  


[avatar]  Blender Dumbass

👁 73 💬 13



Any suggestion about how I can make the website better


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

  Unread  

[thumbnail]


214 / 250 Signatures

[avatar]  Blender Dumbass

👁 13 💬 0



Dani's Race version 2025-01-19


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


[icon articles]The Real Steven Spielberg

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 51 💬 0



Yesterday I went to buy myself a hamburger that I allow my fat ass only about once a month or so. When it was time to take the finished package ( since I prefer to eat at home ) the cashier lady called me "Steven". I blushed and felt both amazing and embarrassing. No, she doesn't know that I do movies and that soon a movie of mine comes out. She has no actual idea who I am. That was the first time I ever saw her. It's just when you order something, their machine asks you to write a name, so they could call you when it's ready. Writing my own name would be a horrible privacy problem. So instead I write names of celebrities. And this time I wrote "Steven Spielberg".


[icon music]The Pentas

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 36 💬 0



Pentas are 5 gong-style round drum-things which are used to play simple melodies. Each one of those is one note in a scale called the Pentatonic scale. If you take the modern western scale with it's 7 notes (Do, Re, Mi, Fa, Sol, La, Si). The pentatonic scale is the best sounding 5 of those (Do, Re, Mi, Sol, La). The easiest way of achieving the pentatonic scale on a piano would be to play only the black keys. Also the pentatonic scale is quite popular with beginner guitarists. It's rather simple on a fret-board and gives a nice sounding solo when improvising. The Pentas - being my second album, was still recorded during my time learning the guitar. So I used a lot of the pentatotic scale in it. Thought quite frankly, knowing about the other two notes (Fa and Si) I added them quite often still.


[icon articles]UPBGE - What is Depsgraph? And How to Optimize for Depsgraph?

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 66 💬 0



You see things like "Physics", "Logic" and even "Rasterizer" and you immediately understand what you need to do to optimize you game. But "Depsgraph"?... It looks like a mysterious thing that nobody knows nothing about. Yet is it one the most problematic things there is in your game. And you are going mad just trying to figure it out.



#DanisRace #MoriasRace #Game #Gamedev #UPBGE #blender3d #animation #GTAClone #programming #python #project #performance #depsgraph


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