[icon ] blenderdumbass . org [icon scene] Articles
LIVE! Making a photorealistic street in Blender | Log [icon peertube] Watch!

The Spaghetti Code Of Dani's Race

December 21, 2024

👁 204

https://mastodon.social/ : 👁 8
https://www.google.com/ : 👁 1
https://blenderdumbass.org/ : 👁 4
https://blenderdumbass.org/articles : 👁 1
https://blenderdumbass.org/articles/the_spaghetti_code_of_dani_s_race : 👁 1
https://blenderdumbass.org/articles/why_night_scenes_in_video_games_are_so_hard_to_make_right_ : 👁 1
https://mastodon.online/ : 👁 1
https://blenderdumbass.org/articles/libre_games_and_making_money:_introducing_petitions : 👁 1
https://blenderdumbass.org/search?text=Dani%27s+race : 👁 1

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

License:
Creative Commons Attribution Share-Alike
Audio Version




[avatar]by Blender Dumbass

Aka: J.Y. Amihud. A Jewish by blood, multifaceted artist with experience in film-making, visual effects, programming, game development, music and more. A philosopher at heart. An activist for freedom and privacy. Anti-Paternalist. A user of Libre Software. Speaking at least 3 human languages. The writer and director of the 2023 film "Moria's Race" and the lead developer of it's game sequel "Dani's Race".


From 1 years ago.
Information or opinions might not be up to date.



Spaghetti code! The insidious thing that often happens even to the best of us. No wander that it happened to me. When programming you want to break your code into functions that could be called from many other places. Doing everything as one large function is a problem, because sometimes you might want to do the same operation or the same check, or whatever, again in another place, and that will require you to copy paste large chunks of code. And if suddenly you decide to change something about those checks, or functions, you have to change that something in all those places one after another. Dani's Race my game, has a bit of a Spaghetti code problem.

For the most part the game is rather nicely organized. The Vehicle.py file for example has tiny little functions and they reference each other all the time.

For example the function that calculates the engine Torque is rather small and it looks like this:

def Torque(car):

    # This function calculates torque of
    # the car. Based on an estimate of
    # a torque curve.

    rpm    = car["rpm"]
    maxrpm = car["specs"]["maxrpm"]
    torque = car["specs"]["torque"]
    health = car.get("health", 1)

    # Nitro
    if car.get("nitroing"):
        return torque * 10
    
    return math.sin((rpm/maxrpm*(math.pi/4*3))+math.pi/4) * torque * health



As you can see it takes the car object as the input, finds the current RPM the engine makes at the moment, find all kinds of specs of this car, checks very quickly if the car does nitro and then does some math with it to output something roughly resembling the real life torque graphs I saw online. Which kind of resembles a portion of a sine-wave.

Compare that to the Main_Update.py where a single main() function spans 1645 lines of code in the released version on git right now, and 1683 lines in the current version I'm in the middle of developing.

For some reason it even has sub-functions inside of it that could live just fine outside of the main() function. For example:

def GenerateDoorObject(i):
        # Location doesn't matter when you dealing with rotation
        doorObject = {"ClosedRotX":i.get("ClosedRotX", i.orientation.to_euler()[0]),
                      "ClosedRotY":i.get("ClosedRotY", i.orientation.to_euler()[1]),
                      "ClosedRotZ":i.get("ClosedRotZ", i.orientation.to_euler()[2]),

                      "OpenRotX":i.get("OpenRotX", i.orientation.to_euler()[0]),
                      "OpenRotY":i.get("OpenRotY", i.orientation.to_euler()[1]),
                      "OpenRotZ":i.get("OpenRotZ", i.orientation.to_euler()[2]),

                      "ClosedLocX":i.get("ClosedLocX", i.position[0]),
                      "ClosedLocY":i.get("ClosedLocY", i.position[1]),
                      "ClosedLocZ":i.get("ClosedLocZ", i.position[2]),

                      "door": i
                }
        # But rotation and location both matter when you dealing with location
        move = [0,0,0]
        moveback = [0,0,0]

        for naxis, axis in enumerate(["X", "Y", "Z"]):
            code = "OpenLoc"+axis
            move[naxis] = i.get(code, 0)
            moveback[naxis] = -i.get(code, 0)

        i.applyMovement(move, True)
        for naxis, axis in enumerate(["X", "Y", "Z"]):
            code = "OpenLoc"+axis   
            doorObject[code] = i.position[naxis]
        i.applyMovement(moveback, True)

        return doorObject


This function generates a door object, so that inside of the game, when Dani walks outside of his room, the door will automatically open up and stuff. It could live as its own function, somewhere in Doors.py but there is no Doors.py. And I nested it inside of main() like an idiot.

But the stupidity of main() doesn't stop there. It has the logic of what to do when game starts in a simple if statement. Which is not short, since the game needs to do a lot of pre-calculations for various optimization techniques to work.

Fine! In this part there are a lot of mentions of Opt.RegisterObject() function from Opt.py that looks like this:

def RegisterObject(object, precision, delete=True):
    
    # This will move an object from scene.objects into
    # chunks. So that they could be stored without
    # effecting the BGE Depsgraph performance.
    
    # Creating chunk
    addr = Address(object.position, precision)
    if addr not in chunks:
        chunks[addr] = {"loaded":False,
                        "objects":[]}
        
    # Adding properties
    virtualObject = {}
    virtualObject["name"] = object.name
    virtualObject["position"] = object.position.copy()
    virtualObject["orientation"] = object.orientation.to_euler()
    virtualObject["scaling"] = object.scaling.copy()
    
    for i in object.blenderObject.game.properties:
        virtualObject[i.name] = i.value
        
        
    # Adding the object to the chunk
    chunks[addr]["objects"].append(virtualObject)
    
    # Deleting real object
    if delete:
        object.endObject()
    
    # Returning
    return virtualObject



As you can see, by itself it is not a large function, but it is mentioned at least 5 times in the main() making it very big to copy paste all around the place. So the fact that it is referenced instead of being copied around, is already good. It already saved numerous headaches for me.

I already started some development on Racing.py which will be a place where I will put most of the racing code from main(). But the funny thing is, most of the racing code is still in main() like the very problematic way I check for whether I can even start a race, by doing these sets of checks:

after = race.get("after")
during = race.get("during")
duringcheck = dani.get("race") == after and Script.Story.get(during)
aftercheck = duringcheck or after in Script.Story["passed"] or not after

if ( not dani.get("race") and aftercheck and Money.Have(bid)) or duringcheck:

    Map.Show(race["starters"][0]["location"], icon="Map_Circle", color=[0.01,0.01,1], ID=racename)
        
if not race["started"] and ( ( not dani.get("race") and Money.Have(bid) ) or duringcheck  ) and race["racers"] and aftercheck:
    
	# Starting the race is possible now.



Similar code is copy-pasted into Vehicle.py too, since it needs to know when to spawn the racers to be around the racing circle. And I could instead have a function somewhere in the Racing.py that will check all it needs to check, and return a simple True or False back. Making the code so much less tedious to work with.


[embedded image]


The thing is, it is not as simple as whether the character is near the spawn location. Some races have bids, which means the player should have enough money to start the race, some races are only available after a certain mission in the story mode is passed. And sometimes it is a during-mission race which will be unlocked later, but has to be temporarily unlocked now.

And there are bugs with all that stuff because I coded incredible complexity and decided not to separate all this logic into it's own dedicated function. Am I crazy? To fix the bugs that I know are still happening with this racing logic, I need to first fix this Spaghetti code.

The main problem though, I am procrastinating and also I am adding new spaghetti code to the main() like, I've added the whole time logic to the main(). I have a part of a hard to work with LOD system in the main(). I'm adding things into the main() while what I need to do is stop for a while and start moving those functions somewhere else. The main() should only do one thing. It should call functions from other places. And do nothing else.

After my last article about this game I started making the police station, where the main character would be taken by the police. Almost immediately I was challenged with changing something about how the game's LOD works. I had to add the police station into the main() into the long dictionary of places, which looks like this:

{"TheRacetrack":{
"object":scene.objects["TheRacetrack"],
"high":"TheRacetrackHigh",
"radius":1000,
"now":"high",
"name":"Papses Racetrack"
},
	
"TheCity":{
"object":scene.objects["TheCity"],
"high":"TheCityHigh",
"radius":1500,
"now":"high",
"name":"Dune Town"
},
	
"TheHouse":{
"object":scene.objects["TheHouse"],
"high":"TheHouseGood",
"low":"TheHouseBorked",
"parent":"TheCity",
"radius":200,
"now":"high",
"name":"Looparound 8\nDani's Home"
},
	
"HallwayPictures":{
"object":scene.objects["HallwayPictures"],
"high":"HallwayPictures",
"parent":"TheHouse",
"radius":180,
"now":"high"
},
	
"Computer":{
"object":scene.objects["Computer"],
"high":"Computer",
"parent":"TheHouse",
"radius":180,
"now":"high"
},
	
"Just3000Wreck":{
"object":scene.objects["Just3000Wreck"],
"high":"Just3000Wreck",
"parent":"TheHouse",
"radius":180,
"now":"high"
}, 
	
"KartingTrack":{
"object":scene.objects["KartingTrack"],
"high":"KartingTrack_High",
"low":"KartingTrack_Low",
"parent":"TheCity",
"radius":200,
"now":"high",
"name":"Karting Track"
},
	
"Karting_Decorations":{
"object":scene.objects["Karting_Decorations"],
"high":"Karting_Decorations",
"parent":"KartingTrack",
"radius":180,
"now":"high"
},
	
"KartingGamesCollider":{
"object":scene.objects["KartingGamesCollider"],
"high":"KartingGamesCollider",
"parent":"KartingTrack",
"radius":180,
"now":"high"
},
	
"KartingGamesObject":{
"object":scene.objects["KartingGamesObject"],
"high":"KartingGamesObject",
"parent":"KartingTrack",
"radius":180,
"now":"high"
},
	
"PoliceStation":{
"object":scene.objects["PoliceStation"],
"high":"PoliceStationHigh",
"low":"PoliceStationLow",
"parent":"TheCity",
"radius":180,
"now":"high",
"name":"Police Station"
},
	
"PoliceStation_Ghost":{
"object":scene.objects["PoliceStation_Ghost"],
"high":"PoliceStation_Ghost",
"parent":"PoliceStation",
"radius":180,
"now":"high"
},
	
	
}
	


Yeah this entire database is just there in main() on line 1473. And the funny thing is, I could somehow make this database be automatically generated by looking at the structure of the game's .blend file. But no, I made it a manual process to put buildings into a dictionary somewhere in a middle of a very long spaghetti text.

I need to stop making the damn police station and clean the damn code of the game. Maybe this is what I will do on my next few livestreams. Because if I keep procrastinating, the game will not go anywhere.

Look at this website, in a few weeks I've done so much progress on it, because I decided to rewrite the entire one file spaghetti code of a server software and make it instead multiple files broken down into multiple functions. Now look at that beautiful code rendering with syntax highlighting and stuff. Look at the system underneath, the federation with blog.madiator.com by @Madiator2011 because now he can use the same server software to build his own blog, the plugin system, the mastodon posts on the home page, the livestream alerts. And all why? Because I stopped with the damn spaghetti code.

Anyway. I think I need to stop writing this article and start working on the game, before this article turns into a spaghetti article.

Happy Hacking!!!



Spaghetti with bacon and spinach by mdid ( CC-BY ) was used for the thumbnail. Also Pasta Tomato ( CC0 )



[icon unlike] 0
[icon left]
[icon right]
[icon terminal]
[icon markdown]

Find this post on Mastodon

[avatar]  Blender Dumbass c:0


I'm happy to announce that 200+ lines of Racing code from main() was moved into Racing.py file today, leaving instead a simple little:

# RACES
Racing.MainLoop(spawnAtDistance)


[icon send]
[icon question]











[icon articles]Dani's Race Franzo Livestream Report

[thumbnail]

[avatar]  Blender Dumbass

👁 50 💬 0



Yesterday ( 5th October 2024 ) Franzo from opensource_gaming did a livestream of a game I am developing called Dani's Race.


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


[icon petitions]Release: Dani's Race v2025-03-17

[thumbnail]


31 / 50 Signatures

[avatar]  Blender Dumbass

👁 369 💬 2



Dani's Race version 2025-03-17


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


[icon articles]So I Cleaned Up Some Spaghetti from Dani's Race Code

[thumbnail]

[avatar]  Blender Dumbass

👁 146 💬 0



A few days ago I had told you that I'm stopping with making the police station in my game Dani's Race because it had a Spaghetti Code Problem. The main() function in the Main_Update.py file was 1683 lines of code long and contained way too many things in it. At the moment, the same function is down to 641 lines of code. This is still way too much stuff in the main() but this is a hell of a lot of reduction.


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


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

[thumbnail]

[avatar]  Blender Dumbass

👁 151 💬 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 articles]A Rant About Making a Multiplayer Game

[thumbnail]

[avatar]  Blender Dumbass

👁 108 💬 1



The multiplayer, or the lack there of, at the moment is so utterly broken and so lacking of being properly made that for a long time, I was just not bothering with it. Seeing it as something unnecessary. Something that does not need to be touched, because other things, like the story or some gimmicky thing is more important for the game than the multiplayer. But people's demands for it didn't stop. So I thought now is a good time to actually properly design it.



#Gamedev #DanisRace #Networking #Multiplayer #TCP #HTTP #Programming #Python #UPBGE #Blender3d #GNU #Linux #GamingOnLinux #FreeSoftware #OpenSource


[icon codeberg] Powered with BDServer [icon python] Plugins [icon theme] Themes [icon analytics] Analytics [icon email] Contact [icon mastodon] Mastodon
[icon unlock]