Home

2024

Worklog

LETSGO Game

Actually Writing Some Damn Code
⌨️

Actually Writing Some Damn Code

Tags
CodeUnrealC++Blueprints
Owner
J
Justin Nearing
🎶
This is part of a ongoing series called Building A Music EngineBuilding A Music Engine

It documents the process of me smashing my head against the keyboard to build a game called LETSGOLETSGO

It’s gotten long enough to break into several sections:

Coding, the last thing a software engineer should do

Ok we’ve gone down the esoteric road of what music is, thought about music in terms of data structures, and figured out how to get Unreal to compile.

By doing this we now have something that the majority of programmers out there are sorely lacking: A Clue.

So let’s write some damn code:

A note on the comment, which is probably a “bad” comment, except in this case the comment came first, where I listed all the scale names and step pattern as comments, then initialized the accompanying object.
A note on the comment, which is probably a “bad” comment, except in this case the comment came first, where I listed all the scale names and step pattern as comments, then initialized the accompanying object.

Here we have the ionian scale (A fancy word for the Major scale). You will notice we have established the step pattern for the scale.

The steps is an enum where Whole = 2 and Half = 1.

The other main enum we have here is the list of notes:

image

I’ve agonized on the naming of the keys, because everything we define here is a fundamental building block for the rest of our music theory engine. A bad name or structure at this point gives you a compounding problem over time.

There does already exist music notes enum in Unreal in the AudioMixer module, but it just flattens all the black keys. Ableton does the opposite, sharpening all the black keys.

Given two competing standards, the obvious choice is to create a third standard!

Obligatory XKCD

Really it doesn’t matter what the standard is, what matters is the consistency. For instance, making the decision to structure the note names based on the circle of fifths means when I export sound files from Ableton, I should match the name: no A#, name it Bb.

Hell I could represent the notes as do-re-me.

Given a Tonic key from ELetsGoMusicNotes and our step pattern found in the Ionian scale, we can generate the correct scale.

Generated Scales

image
  1. Its a struct.
    1. This feels like more of a data container than whatever a class should be, so I’m thinking of it more as a struct.
    2. It’s hard to tell when something should be a class vs. struct
    3. Structs appear to be reflected in Blueprints easier though?
  2. Don’t see GENERATED_BODY()
    1. It is Unreals version of Python’s self (It’s fancy-work Unreal uses to reflect this struct into Blueprints, etc.)
  3. You can start to grok how Unreal is interacting with C++ here
    1. Macro the things you need to surface in the game.

We’ll generate scales into the FLetsGoGeneratedScale struct- a data object that contains the Tonic, Scale, and Notes of that scale.

We need a function that generates the Notes contained in the generated scale:

image
  1. This is similar to the Jippity output I referenced in 🔢Programming Music: Constant Data, but I actually wrote this myself.
    1. Can you spot the bug in Next?
    2. answer

  2. I had some trouble figuring out where to put this.
    1. I originally wrote it as the constructor for FLetsGoGeneratedScale , but there was some unexpected behavior when I brought this into Unreal land.

I ended up defining this as a static function in ULetsGoMusicEngine:

UFUNCTION(BlueprintCallable, Category= LetsGoBlueprintCategory)
	static FLetsGoGeneratedScale GenerateScale(const FLetsGoMusicScale& Scale, const FLetsGoMusicNotes& Tonic);
💡
Right now I only really have the one class- this ULetsGoMusicEngine. This is probably not great, I should probably have a bit more care being put into what classes are what. But as an initial starting point, Im absolutely fine with it. What I really really definitely want to avoid is premature optimization by making this code “cleaner”, which is almost guaranteed to make actual extensions to this code more difficult. When I have a task that makes refactoring this code necessary, then I will refactor it. Clean code ≠ effective code

In theory, we can now find a list of notes in FLetsGoGeneratedScale - a thing we should be able to find Blueprints.

Corrupt Ass Blueprints Brought To You By PAIN

Loki, God of tricks and pranks and stuff turned its attention to my project at this stage.

I did… something.

I had Blueprints working one night, now they are acting very unreliably.

When I start my project, it’s not able to find any of the LetsGoMusicCategory items. If I compile while the editor is running, then my LetsGo stuff appears correctly. The problem is Unreal freaks out every time its missing the LetsGo stuff, and any blueprint Node related to my custom stuff built here goes missing or ERROR or simply ends up in a uncompilable state.

It makes me think there’s different kinds of compiling? There is:

But I still want to build for Editor.

I dunno, the whole thing is finicky in a way that’s hard to describe. Worse still I’m not totally confident that source control is picking up all the changes- super hard to confirm without another computer to build on. That matters because I kind of want to nuke from orbit, reinstall and clone from main.

This is what I’m talking about: I create a variable using one of my data structures:

image

Compiles green, I save all, all good.

Restart Unreal:

image

With the helpful compiler message:

image

Cool.

Real Cool.

That’s cool.

So much for actually writing some damn code.

Clean up Aisle LETSGO

Ok I think if we can do some kind of clean rebuild, we might get this working.

Stumbled on this:

I closed everything, and renamed the derived folders mentioned above to have a _ in front of it:

image

Launch Unreal, get the Module rebuild window, pressed rebuild.

It looks like that this worked. The variables I complain about before appear to be working correctly.

Cool.

So now I can delete those _ directories and move on with the damn task.

💡
When you do this, you get that 💥Unreal C++ missing modules popup. Pressing Yes this time actually does rebuild the thing though. BUT IT LOOKS LIKE NOTHING IS HAPPENING. Patience is the hardest skill to learn, so-called senior dev.

At this point I just kind of got it working

Cool.

Blueprints were recognizing my code, and I built a SpawnPool thing.

The intent for it is to kind of be like an Object Pool.

Object Pooling is a game dev design pattern useful for things like spawning bullet objects. If you only have 12 bullets in a clip, you create an object pool of 12 bullets one time at the beginning, instead of spending resources on creating a bullet every time to fire the weapon.

I imagined that instead of bullets, we have Notes.

Basically I imagined a fancy array to stick the Notes in, then have the MusicPlatformSpawner ”pop” the first note from the array:

image

SpawnPool is a Blueprint Component of the MusicPlatformSpawner and has the BP function Pop Next Note

image

Pop Next Note grabs the first item (the 0th) from the Note Pool and removes it from the array.

Shout out to the Unreal Devs for dynamic arrays. This relatively simple/common action smells like it would be a #wholething in native C++

image

NotePool is generated at the BeginPlay event. It is a SpawnPool Variable that is an array of Notes. Here we set a tonic and for each supported scale we defined in FLetsGoMusicScales, we generate the notes and add them to the NotePool array.

I did have a problem here where every music platform being spawned was choosing the note C. Turns out I had a bug in my generateScale function where I was missing some parenthesis.

But once I fixed it things started working.

I immediately blasted social media with how damn smart I am:

And so that’s it. We did it: A rambling 4 part series on how to make a single function in Unreal.

Gamedev ain’t shit.

Except it is:

Pointers Are Hard