It documents the process of me smashing my head against the keyboard to build a game called LETSGO
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:
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:
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!
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
- Its a struct.
- This feels like more of a data container than whatever a class should be, so I’m thinking of it more as a struct.
- It’s hard to tell when something should be a
class
vs.struct
- Structs appear to be reflected in Blueprints easier though?
- Don’t see
GENERATED_BODY()
- It is Unreals version of Python’s
self
(It’s fancy-work Unreal uses to reflect this struct into Blueprints, etc.) - You can start to grok how Unreal is interacting with C++ here
- 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 Note
s of that scale.
We need a function that generates the Notes
contained in the generated scale:
- This is similar to the Jippity output I referenced in Programming Music: Constant Data, but I actually wrote this myself.
- Can you spot the bug in Next?
- I had some trouble figuring out where to put this.
- I originally wrote it as the constructor for
FLetsGoGeneratedScale
, but there was some unexpected behavior when I brought this into Unreal land.
answer
I ended up defining this as a static function in ULetsGoMusicEngine
:
UFUNCTION(BlueprintCallable, Category= LetsGoBlueprintCategory)
static FLetsGoGeneratedScale GenerateScale(const FLetsGoMusicScale& Scale, const FLetsGoMusicNotes& Tonic);
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 codeIn 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:
Compiles green, I save all, all good.
Restart Unreal:
With the helpful compiler message:
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:
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.
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:
SpawnPool
is a Blueprint Component of the MusicPlatformSpawner
and has the BP function Pop Next Note
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++
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: