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





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












[icon reviews]Bad Boys 2 is Michael Bay's Magnum Opus

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 17 💬 0



Similarly to Michael Mann's Miami Vice Bad Boys II is about love, man... It is about how both Will Smith and Martin Lawrence love Gabrielle Union. And how they are willing to do international terrorism and kill many many people, to save her. True love bro!


#BadBoys2 #BadBoys #MichaelBay #WillSmith #MartinLawrence #Bayhem #Action #Film #Review #Movies #Cinemastodon


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

  Unread  

[thumbnail]


236 / 250 Signatures

[avatar]  Blender Dumbass

👁 94 💬 1



Dani's Race version 2025-01-19


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


[icon articles]Statistics On Moria's Race So Far

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 32 💬 1



Moria's Race was released 118 days ago on 26th of August 2023. And as of now, I'm aware only of about 604 views across 6 platforms that I track.


#MoriasRace #blender3d #animation #statistics


[icon malware]Planned Obsolescence

  Unread  

[thumbnail]

[avatar]  Blender Dumbass

👁 23 💬 1



A lot of electrical devices today have a switch of some kind to deactivate the device after a certain period of time. Sometimes the device just gets annoyingly slow. In any case, it is implemented in software on that device. And the reason for that is Planned Obsolescence. Forcing the user to throw away this device and buy a new one.



[icon petitions]Release: Dani's Race v07-07-24

  Unread  

[thumbnail]


290 / 200 Signatures

[avatar]  Blender Dumbass

👁 14 💬 0



Dani's Race v07-07-24


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