Yes we know Morris is still invisible, sorry! Patch soon..hopefully. I mean it can’t take 6 months again, right? Yeeah sorry about that - a lot of stuff has happened for me (Alice) in the past several months and (without getting too much into details), I just haven’t had the same kind of time as what I had nearer the start of the year - I haven’t abandoned Rolled Out, promise - this game’s still very much a passion project for all of us and I apologize for not really mentioning this sooner (it’s scary!).
---
instead of being empty when going over 999 speed unitsThings have been crazy over the last year, for a number of different reasons. I feel like such a different person than I was when I first started working on this project… it happened bit by bit, so I never noticed.
Well, it’s just snow here.
Anyways, why don’t we take a look at everything that happened in 2021? I could use a trip down memory lane.
We launched with 150 stages and one playable character. Now…?
We’ve added quite a sizeable amount of new content, huh? And naturally, more is on the way. More playable characters, more stages…
Here’s a look at the characters we’ve added. Morris has a lot of new friends now, huh?
Look at some of those community generated numbers, though. That’s way too many custom levels!!! What in the world have you folks been up to, these past few months? You’ve made more custom stages than the game has officials?!
If you want to become more involved in the community, and try some of these custom levels yourself, go ahead and pop over into our official Discord server.
Here’s a little collage I generated of just a small fraction of them.
We didn’t just add new content, either. A bunch of our in-game stage backgrounds received a great deal of revision over the course of last year.
You might be wondering what that last one is. It’s a little embarrassing to admit it, but that’s the Bonus stage background. We might have forgotten to… well, implement, Bonus stages. So, expect the return of the famous Bonus in a future update!
Speaking of backgrounds you might not have seen before…
Here’s new one. You can call it “Volcanic Chamber”.
Well, that wraps up our year in review. You might be curious what our plan is moving forward, though. If you want, you can keep an eye on our GitLab Issues page to see not just our list of bugs to be fixed, but also our roadmap for future updates.
If you don’t feel like sifting through it, though..
We’re shooting for online multiplayer in our next major update, with rollback netcode. No promises on the timeframe, because gamedev is fickle business, but we’ll hopefully have something for you all soon.
Bye for now!
]]>none
seesaw axis has been deprecated in stage configs - see the stage config changelog on the docs for more info.Hey, you, there, reading this. Want to win a cool, unique, and not for sale Rolled Out Shirt? Keep reading.
We’re holding our first ever leaderboards contest in Rolled Out! The official 2021 Rolled Out Go Fastathon! Many of our courses have been optimized to the ends of the Earth, so it wouldn’t be a fair contest. We decided that for the sake of keeping the playing field even, we’re going to run on the new levels added in patch 0.5.4.1, available in the “Basic” arcade course. 15 never before seen stages, 15 new chances to prove your speedy skills, 15 stages to optimize and perfect. Make sure to take breaks, and remember the wise words of Confucius. “Going faster than light will only leave you in darkness”
…he probably said that, at some point.
We’re still finishing up the shirt design. It unfortunately couldn’t be ready for our release date, so we’ll be updating this blog, our Discord, and Twitter with the final design soon.
So how does this work? Easy. First, you have to sign up here - this competition is purely optional to everybody. You then have to be on top of the time leaderboards for the new course by the end of the contest (2021-08-13). So sign up, go fast, and win some sweet shirt based prize wear.
The top 5 times on the leaderboard for the new “Basic” course, who have signed up to be in the running, will get a shirt, so get rolling out or be left in the dust!
]]>Never rolled an animal in a ball before? Good. That’s normal. But it means you might have some trouble playing this game… Twilight has been hard at work to make a new set of beginner levels to help players new to the genre develop some animal-filled ball rolling abilities. Crue has also been hard at work improving the menus experience for controller players. It’ll be a lot easier to adjust your bindings through the in-game menus, now.
What’s going on?
So as far as Patch 0.5.4 goes, what’s going on? What do we have behind the curtain that didn’t make the cut for this release? To start, we’ve gutted all of the engine’s math, and substituted it with our own! Because, historically, any time we’ve run into a problem with an existing system, our solution has been to remake it from scratch. This is an insane practice, and you shouldn’t use us as inspiration.
So, we’re implementing something called Fixed Point Math. Most 3D math is done using ‘floating points’, which are a computer’s way of representing a number with a fraction. But they aren’t always deterministic between different computer types - doing the same calculation across different processors might have a slightly different result. Fixed Point Math will eliminate that divergence, so what happens on your computer will happen in the same way on your buddy’s computer, and their buddy’s computer, ad.infinitum.
Having this level of consistency in our math is no easy feat, but it makes things like replays and multiplayer possible. Your gameplay, replays, and your friend’s gameplays will all sync up perfectly based on inputs alone. This is all in service of allowing us to have stable, low latency replication, alongside consistent replays. And if we do it right, you won’t notice a difference in how the game plays or feels.
It isn’t ready quite yet, though… we have to replace a lot of math in our code, and it’s long and tedious work. Consequently, we opted to split what would be patch 0.5.4 into three parts. Part 1 is more levels, and open communication about what we’re up to. Part 2 is finally getting Fixed Math in your hands, and then part 3 will be the long awaited multiplayer update. We hope you’re as excited as we are, until then please enjoy the new levels.
If you have any questions, feel free to ask us on Twitter, or on Discord!
Want to help out?
We’re looking for feedback on the user interface of the game - in particular, to improve the practise menu, but we’re also gathering feedback on other sections too. If you don’t mind sparing five minutes, shooting over your thoughts over at https://airtable.com/shrzEsNrzrrgYdV5U would be much appreciated!
warp_distance > 1
) now have particle effectsPatch 0.5.1 is mostly focused around responding to player feedback regarding some stages, fixing some options, and correcting some of the more obvious bugs. Your best scores are now saved, and menu music has been added! We also fixed a couple of crashes, did a couple of things that may help increase performance, and even changed how your times are displayed. Please continue to provide feedback and bug reports through the Discord, email and GitLab!
The framerate cap slider in the options menu now goes up to 240 (up from 120), beyond that makes the framerate unlimited
The default framerate cap has been changed from unlimited to 120
The coin count in the game HUD now uses at least 2 digits, to line up with how the life counter uses at least 2 as well
Improved lighting in Desolation
Tranquility BG shrunk dramatically
Ice Cave BG shrunk dramatically
Changed layout of the following stages:
Changed cycle in the following stages:
Reduced wind strength in the following stages:
Made latter two seesaws in “Foursquare” a little less aggressive
“Snake” renamed to “Wavelength”
“Link” renamed to “Basics”
“Conveyer Belts” renamed to “Conveyor Belts”
“Icey Bridge” renamed to “Icy Bridge”
“What The Hell Do I Name This Thing” renamed to “Decomposition”
“Quicksand” renamed to “Not-So-Quicksand”
“Punched” renamed to “Swervy Pistons”
The “Course complete!” dialog now shows milliseconds to 3 decimal places, instead of 2
Times in the UI are now formatted like 00h 00m 00s 000ms
, instead of 00:00:00.000
With 0.5.0.1, this multiplier has been increased to 1.5. Existing saves will have their multiplier bumped up to 1.5 if they started at 1.0, however if you prefer, this can be reverted back to 1.0 and the game won’t touch it afterwards.
Full changelogs can always be viewed at https://docs.rolledoutgame.com/changelog/
]]>…”some” might be an understatement. It’s basically all I do outside of Rolled Out! work, now. These are all individual animations I’ve completed for a custom character I’m working on. Character animation is a lot of fun!!!
Maybe, after Rolled Out! is done, we’ll make a platform fighter. That’ll be the day…
Anyways, let’s jump into the update.
The main reason that this update was late, is because we were working on wrapping up the implementation for a few new systems! Let’s run through the two most notable ones.
We now support conveyer belts that are directed along the UV mapping of the surface. Essentially, this just means that conveyer belts can curve in any which direction the level designer wants. (This was not possible before… a conveyer belt could only push the ball in one direction.)
However, worth noting… that effect with the texture scrolling along in the direction of the conveyer belt is NOT a consequence OF the conveyer belt. That’s an entirely separate matter… we recently revamped our material system a little bit, to allow for any vector or scalar property to be animated. CraftedCart has a little writeup about it to share with you, in fact! Take it away.
I didn’t really have much of interest to say for the last few weeks, mostly boring backend stuff and bashing my head against a wall and getting nowhere >.<. This time though, hooh I have something fancy looking.
So, while we had conveyor surfaces in the game, we didn’t really have a way to convey (ha ha) to the player whether a surface was a conveyor or not, such as by having the texture scroll. The way I saw it, there were two ways we could handle this: we could add a simple X/Y scroll speed option to materials, oooor I could go and wire up our animation system to materials. …yeah I went with the latter. :)
And so now, every material property that could could manipulate for custom stages, can now also be hooked up to animation curves (excluding textures… you can’t exactly animate a texture with a curve). Want to animate texture scroll? You’ll want an animation that uses the path parameter_overrides.vector2.tex_coord_offset.x
(or .y
). Opacity? No problem, as long as you’re using a translucent material. Refraction? You can make some trippy looking things with that. :3
(Can you tell I’m having perhaps a bit too much fun with material animations?)
Anywyas, we’ll have technical docs for all the properties you can use and how you can animate them, I promise, so those of you who may like making custom level can muck around with this.
See you all in March.
]]>It’s a fun way to keep my animation skills sharp!
Let’s jump into the update.
So, the big thing is that I’ve been working closely with a friend to bring a new character to the game!
You can’t see her just yet, but it’s only a matter of time before her proper unveiling…
Apart from that, it’s just been the usual pattern of bug-fixing.
Not much else to share, I’m afraid. We didn’t even get a CraftedCart writeup this time…
Well, I have reason to believe things will speed up very soon. Look forward to it, please!
See you all in February.
]]>I’m sleepy, so let’s jump into the update.
Most of the past two weeks has been spent cleaning up our Issues list.
It’s not particularly interesting, sorry to say - just a lot of bug fixing. However, we do have something small to show off, courtesy of CraftedCart!
Hey, welcome to the new year! I hope you all had a good holiday season :)
Stuff’s been winding down around this time of year, so there’s not gonna be too much from me here (eehe.. I feel like I’m saying this on almost every blog post we do now >.<). Hoow about we start over here.
A little touch, but springs now animate in the direction of their impulse. Not really a lot more to say about this so let’s move on to…
More recently, I’ve been making a little preview for your camera settings and really not having a fun time with drawing custom UI in Unreal (woo for a complete lack of documentation!). As you might imagine, trying to tinker with your camera settings without being able to see what you’re doing isn’t the easiest thing to do - setting all the sliders to their extremes can even leave the camera in weird places such as below the ground! Hopefully this helps a bit, though I’ll certainly need to prettify it up some first. ;P
See you all on the 15th.
]]>By the way, go watch Queen’s Gambit.
Let’s get into the update!
I wrapped up the goalposts for the final two worlds which still lacked one.
You’re going to have to be a little more careful when you reach the goal on some of these stages.
I’m sure you’re excited to see some more of our late-game levels! I’ve been hush-hush on the harder stuff, but it’s been a while - we can afford to share a little peek.
Also, as per usual, we have a writeup from the most wonderful CraftedCart. Take it away!
Thinking of stuff to write about for these dev updates is really tricky when I’ve just been doing boring ‘ol bug fixing. >.< Hmmmmm…. oh, here’s a fun one from a little while back!
The minimap! It’s the map… that’s mini!
You’ll have to excuse the mismatched background here, the latest Nvidia Linux drivers have been uhh… well you don’t want to see what this looks like in the ice background, and I’ve just been too lazy to downgrade them again.
Now, you may notice one thing about that minimap… it doesn’t show the bottom of the stage. You may have seen sometimes in games or 3D rendering that there’s a cut-off point in the distance which you can’t see anything beyond - the “far clip plane” it’s called. There’s also a “near clip plane” where you can, well, see through objects if you get too close to them. The reason for having these clipping planes is that we only have a limited amount of bits to represent numbers to represent depth between the near and far plane - a smaller range between the near and far plane gives us more precision, whereas a farther range lets us see further/closer.
So naturally, my first thought was maybe the camera that was rendering the minimap had a far clipping plane that was too close. As it turns out however, that plane isn’t adjustable, and is instead fixed at infinity (however that works)!
…a little bit of poking around later, I figured I’d take a look at how the minimap is even rendered to.
So.. just a brief primer on image channels if you’re not aware: an image can have multiple color channels - usually you’ll have an R/G/B channel to hold the red/green/blue value of each pixel, sometimes you’ll also have an A channel for alpha (opacity). In this case, we have 4 channels to play with here (RGBA) and if you look at “Capture Source”, the minimap is set to render RGB color into the, well, RGB channels, and the depth of each pixel into the A channel (how far away from the camera each pixel is). So that’s interesting I guess…
Next stop: let’s take a looksie at how the minimap material actually works (I’ve added in a fake transparent checkerboard in the background to help visualize how this looks).
So, the gist of what’s going on here is the RGB color of each pixel is just identical to the RGB color of each pixel on the rendered minimap texture. Each pixel can also either be visible or hidden, depending on the “opacity mask”. Here, we divide the depth of each pixel by 24000, throw away all pixels that are further away than 24000 units, then throw away all pixels that are outside of the white circle texture at the bottom (since the minimap is circle-shaped in the UI).
So if the cutoff point is 24000….magical-units, then let’s try increasing it! Why not 99999 magical-units?
…ah, that’s why. Didn’t exactly want a black circle around it.
So, at this point I started playing around to see how far I could raise the cutoff point, before a black circle appeared around everything. Skipping past all that however, I eventually found out that the minimap texture was stored with a format of RGBA16f. What this means is that there’s the afformentioned RGBA channels, but also that each pixel in each channel is stored as a 16-bit floating point number - and with this info, I can find out what the maximum possible value for the magical-units are (65504).
So, let’s subtract one for safety, and divide by 65503 instead, aaand…
…perfect!
…wait what do you mean Unreal can just render to a texture with opacity, instead of needing to do this depth workaround thing…
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa [jumpcut]
Anyways, with that out of the way, there was still one more issue: additive materials (the slightly glow-y ones found everywhere in the VR world) don’t show up… at all.
So, additive materials don’t write to the “depth buffer” (a texture that keeps track of how deep each pixel is), so we can’t see them in the alpha/depth channel in our render target. In the end, I just decided any pixel that wasn’t completely black should be visible, even if the depth value was out-of-range.
Perhaps not the prettiest (given additive materials show up as opaque-against-black now on the minimap), but waay better than before. This’ll do just fine!
We’re going rowing.
See you all in the new year!
]]>…sorry, I was shaving.
Anyways, we have a lot of awesome stuff to show you today! Let’s head right into it.
We’ve basically finished implementing our updated material system, giving the level designer a great deal of flexibility in the kinds of visual effects they can use in their levels.
With clever use of these material types, combined with the right textures, you can pull off some neat looking stuff.
It’s also just useful for detail and effects to help visually aid the player in the level. This gate floor provides ample sight to the platforms you’re supposed to drop down to.
Here, scrape marks are used on the floor to give a visual cue for the seesaw walls that the player can turn by running into.
We also finally got around to implementing VFX for wind. No more invisible forces pushing the ball around! It could probably still use some improvements, but it’s certainly better than nothing.
Wormholes are now basically fully implemented. There’s still some polish to be done on the visual side of things, but they’re pretty spicy already!
Also, the announcer now yells at you when time is running out on the stage. I’m too lazy to record a video of that, so you’ll have to imagine a dude counting down from 10, and then saying “Time Out!” in a really condescending manner.
There’s probably more to account for, but I can’t really recall it right now. Oh well - more stuff to throw onto the next devblog!
Of course, we can’t leave home without a writeup from CraftedCart. Take it away!
Rolling back around (hah!) to our input system again, something that has never played well with before was the game’s UI. Well, as of just a few moments ago, I can say that it plays slightly less worse now! ;P Anyways, the gist is that Unreal has its own input system that its UI is closely tied to, and we have our own thing going on since we want to support a wider range of controllers - somehow we needed to glue the two together a bit. What follows is just a dump of thoughts I had and how eventually we got it to kinda-mostly work.
If you haven’t seen Tom’s video on how he bodged together an emoji keyboard, what basically happens is when you press a key on a keyboard, a program called LuaMacos intercepts that, figures out which number emoji you want to type, saves it to a file on disk, then presses the F24 key (yes, there’s that many function keys). From there, AutoHotkey intercepts the F24 key, reads that file, looks up which emoji to type, and types it. It’s ugly… but it worked! I was thinking about how we could maybe do something similar in our game.
So the idea was if you press a directional key, an accept/back key, or any input relevant to us, our input system would pick up on that and save the direction/action to a variable somewhere in memory. Then, it would simulate a key press that Unreal understands - one that players almost certainly wouldn’t have on their keyboards. Unreal doesn’t recognize F24, so maybe something like… EKeys::Daydream_Right_Trackpad_Click
- you’re not gonna be playing this game on a Daydream headseat anyway. Then, from the UI scripts, we could listen for that key press, read that variable in memory, and figure out what we’re supposed to do from there.
I… never even got as far as writing a single line of code for this idea, as I kept digging to see if there was a better way we could handle this. That was when I found…
…that it was relatively simple to add your own key types to the engine (with EKeys::AddKey, if anyone’s curious). So what if instead, I made custom keys for menu navigation in all 4 directions, and just simulated pressing them from our input system? While we’re at it, also make it so Unreal should respond to our custom keys when figuring out UI navigation, instead of the default arrow keys, gamepad left stick, etc.
So for starters, we create the keys…
1 | UCLASS(BlueprintType) |
…then we make a custom navigation config that says UI navigation should respond to these keys we just made…
1 | class FPolarNavConfig : public FNavigationConfig |
…and finally, we register the keys and config on game startup…
1 | void UInputManager::Init() |
…oh and, don’t forget to unregister the keys when the game ends.
1 | void UInputManager::OnEnd() |
Sweet, now we’ve got that in-engine, we just need to simulate pressing KeyPolarLeft
, KeyPolarAccept
, etc. Uhh… how?
Well maybe we can call FSlateApplication::OnKeyDown to simulate a key press… except, that takes a key code and a character code, which our custom keys don’t have. How about FSlateApplication::ProcessKeyDownEvent? Hrm, looks like it also needs a key code/character code, but it also needs an FKey
, which I have! Soo, maybe I could get away with this if I just say the key/character codes are zero?
Weeelll that… didn’t work. The engine does seem to pick up on the key presses, though they didn’t seem to affect UI navigation at all. Ooook, next idea!
…seems like an obvious solution to any programmers who may be reading this, right? …right? The issue here is that navigation requests are typically done by each UI component itself as a reply to… some system after handling a key down event. It looked like a lot of the ways Unreal pokes at UI navigation were private - I couldn’t touch! But eventually, after a lot more digging around, I found FSlateApplication::ProcessReply. So now the idea was to try and fake a “reply” to get the engine to navigate in the UI for me.
Well, faking the reply itself is fairly simple. I just make a reply and tell it I want to go in that direction, from the currently focused UI widget, and pretend it comes from the keyboard…
1 | FReply::Handled().SetNavigation(Direction, ENavigationGenesis::Keyboard, ENavigationSource::FocusedWidget) |
…what’s trickier is the needed data surrounding that that this ProcessReply
function needs. Most notably, I need to figure out what the currently focused UI widget is. That’ll be simple, right? There’s probably just a function somewhere that’ll tell me what’s focused, right?
Weeelll… yes, and no. While for most desktop purposes it might make sense for only one widget to be focused at a time ever, Unreal lets multiple widgets have focus, one per user. Perhaps a bit surprising at first but I guess it makes sense if you want multiple players to both control bits of the UI at once (say, for example, in the character picker in Smash). Anyways, to get the focused widget here, I kind-of make some guesses here…
1 | TSharedPtr<SWidget> FocusedWidget = App.GetUserFocusedWidget(0); |
First, check if user ID 0 has a focused widget. If not, then we see what the keyboard user has focused, and failing that, we just give up.
Anyways, the other bits of data needed for ProcessReply
are fairly easy - we need…
0
aaand finally, after all of that faff, simulating UI navigation works!
…unless you’re using the arrow keys GOSH DARN IT nevermind, of course I find it just as I’m done writing this. And when I say I find it, I mean I have absolutely no idea what I did! But it works now.
Did you know that they made horses a real thing after Minecraft implemented them? Insane what science can do these days.
See you all in December.
]]>I’m rather exhausted since I just finished my daily exercise routine. (It’s Dance Dance Revolution 1 hour, OK?) So we will just jump right into the update, if that’s alright with you.
We’ve been working on more back-end stuff, so there isn’t a whole lot of fanciness to show off this week. Check out the updated materials for sticky floors and gravity floors, though!
Here’s the usual awesome writeup from CraftedCart - this time, talking about image previews for stages.
Y’know what the level-select menu has been missing the whole time? Preview images of the levels! You can’t just expect me to remember what each level is by its name, right? Problem is…we have a lot of them - a few hundred levels. You miiight be able to convince me to go through and take screenshots of all of them by hand, if you were persuasive enough and I was in a good mood, but y’know what’s a better idea…? Automating it!
After all, we already had systems in the game to a: flip through all the levels in a course, and b: do a spin-in cutscene showcasing the whole level for a few seconds before you started playing it. We could just reuse that, right? And thus, the “screenshot” game mode was born - a hidden game mode in addition to the not-so-hidden arcade and practise modes.
It’s certainly not the prettiest mode, though not that it really matters. Launching into screenshot mode with a course will lock the camera’s field-of-view to 50, start playing the spin-in cutscene, then after a few frames, save a 512x512 screenshot in the level folder named preview512.png
. The next frame, the next level in the course is loaded, then a few frames later it takes a screenshot of that, etc, etc. That’s it! At the end you can even try to play on the last level - I’m just using the same ‘ol spin-in and spawning logic since, well, it just worked for taking screenshots - no need to do anything special for a developer mode.
Now that we’ve got the screenshots, all that’s left to do it, well, load them on the level-select menu. A fairly simple task, made a little bit complicated by the fact that we don’t want loading preview images to freeze the game for two or so seconds when flipping between the different worlds in the menu. A little bit of fenangling with loading images on multiple threads later though, aaaand…
…tadaa! Nice.
I’m very sleepy, and my voice hurts.
In the meantime, check this out:
Thanks for reading, and see you in November.
]]>Today, we’re going to be talking about *”Coin”* and *”Hole”*. But before we move onto that, we have to talk about the HUGE news that has been taking the gaming scene by storm. The news everybody is talking about. It was revealed so recently, haven’t you heard about it? Who knew a game that began with its roots in the indie game scene could have made it so far…
That’s right. Rivals of Aether Definitive Edition has released. Please, go check it out!!! This game is awesome, and it deserves so much love for being an amazingly original and standout title within the platform fighter genre. It’s even on Switch! There’s no excuse not to play it!
Huh? You thought I was referring to that new Smash Bros. character? I don’t even know what the hell you’re talking about.
Anyways, let’s jump right into the update.
Coins are back in, and stronger than ever. We still haven’t finished the models for each individual world, so until we get to that point, they really will just look like coins everywhere you go. But they look pretty awesome, anyways!
We also now have holes.
They work pretty much perfectly. I hope you like our holes.
Aside from those two things, we’ve just been focusing on lots of little polish-related things. There’s now a particle effect around the ball whenever you’re being actively affected by a gravity floor.
Naturally, following this, we’re going to have a writeup by our very own CraftedCart. Take it away!
I was hoping “Infinality” was an actual word when coming up with the title for this section, but alas, it isn’t. Anyways, recently I’ve been messing around with getting music playing in-game - seems simple, right…? right??
So, the way music works in our game is that there may be an intro section of music, followed by a looping section that repeats forever. These two sections are stored as different files for each song.
Now… the tricky part is, how do we play the intro section, followed by the looping section, without any gap between the two files?
So, my first thought after doing a bit of searching online was perhaps I could use the “concatenator” node in a sound cue (where a sound cue lets you manipulate and combine audio playback in Unreal Engine). The concatenator node is simple: it plays its first input, then its second, then its third, etc. in order. Soo, I simply wire up the intro section and looping section into the concatenator, aaand…
Hmm. That doesn’t quite sound right, does it… Turns out while the concatenator does play sounds sequentially, it cannot do it seamlessly - moving on…
Next idea: how about using a “level sequencer” - that thing in Unreal most often used for cutscenes and other cinematic elements. What if I tried just using it to sequence 2 bits of audio together - I can figure out how to make the second section loop later…
…maybe not, then. Even doing something ludicrous like setting the sequencer’s framerate to the same as the audio sample rate (44100 Hz) doesn’t help.
After a fair bit more digging online, I eventually came across a plugin known as “TimeSynth” - it comes bundled with Unreal, disabled by default, and can do sample-accurate audio stitching. Well, that sounds exactly like what we want, doesn’t it! Let’s give it a listen…
…oh heck yeah! That sounds pretty seamless to me.
So, the way TimeSynth works is you define a BPM for the audio you play, and queue up audio. Here, in BeginPlay
, I set the haunted grounds intro sound playing, and also register a “quantization event delegate” (which basically means every beat or however long, I want to run some other code). After 4 beats, we want to play the looping part, so every beat we increment the BeatsPassed
counter. Once that hits 3
, we queue up the looping sound to start playing on the next beat.
So… that works, right? All sounds good, just gotta punch in a bunch of numbers into the engine and we should be good to go, right? CraftedCart…. why does your article go on for several more paragraphs? CRAFTEDCART???
Hahaaa, I’m a programmer - it’s my nature to see the silliness in this and engineer my own solution. ;P After all, doesn’t it seem a bit unnecessary to specify the BPM and length of each song when I… just want to play one audio file immediately after another ends? Besides, it starts getting really finicky when we have to deal with intro sections that aren’t an exact multiple of a beat long.
So, I dove in to the TimeSynth plugin’s source code and started digging around, seeing how TimeSynth managed to play audio seamlessly and making my own plugin to play audio one-after-another without the need to specify durations or timings or other nonsence. But first, a detour…
You probably know how sound works: you have a sound wave that goes uppy-downy, that goes into your ear-holes, and that lets you hear the screeches of a crying baby on the other side of the plane while you’re just trying to relax.
When working with audio digitally, what we do is we pick a sample rate - in our case, the music we have has a sample rate of 44100 Hz. This means 44100 times per second, we have a sample of how high or low our sound wave is at that point in time. We can send these samples off to the hardware and that’ll make your speakers move back and forth depending on the value of each sample. For obvious reasons, a higher sample rate sounds better since you’ll be able to better capture the fluctuations in the sound wave - 44100 Hz was the sample rate most commonly used on CDs, and that’ll do for us too!
Now, there’s several ways of storing audio samples. Using a signed 16 bit number is common (that is, an integer between -32768 and 32767). Sometimes, a floating-point (decimal) number between -1 and 1 is used instead. Unreal generally seems to work with the latter.
So, finally get get to how music in Rolled Out works. We have a subclass of USynthComponent, USequencedAudioComponent I call it. This component can be given a “sequenced audio asset”, which is just a list of audio files to play in order. When you’ve done that, the sequenced audio component will start decoding both the first and second sound files to play, known as the “Now” file and the “Next” file (the now file being the file that should be currently playing, the next file being the one queued up to play immediately after the now file has finished).
Periodically, the engine will ask to be fed some audio samples such that it can play them. Most of the time, this is a simple affair - I take the samples I get from the decoder decoding the “now” file, and feed this straight into the audio buffer. When we near the end of a file, this gets a little more complicated, but not much.
Lets say the engine has asked for 200 samples for example, but we’re near the end of the “now” file and there’s only 50 samples left. At this point, the “next” file becomes the “now” file (as that’s the file that we should start playing from), we fetch the third sound file from the sequenced audio asset, and if that exists, that becomes the new “next” file and starts decoding. We then fill out the remaining 150 samples in the buffer with the beginning of the new “now”
file.
(The game… doesn’t actually have any audio split into 3 or more files, so in practise, that part is never used)
tl;dr: We decode 2 audio files at once such that we can seamlessly stitch the first file with the second one.
So… let’s give that a listen, shall we?
Perfect.
It’s my birthday in 5 days! Yay!
Thanks for reading, and see you on the 15th.
]]>Speaking of birthdays… did you know that it’s the 5th anniversary of UNDERTALE’s release? Go play it if you haven’t, yet! It’s a huge source of inspiration for me as a writer, as well as my sense of humor. I basically spent the last 12 hours reminiscing over all of my old UNDERTALE fan art with friends… listening to the concert… We all sat in a voice chat and cried for 3 hours straight, essentially.
Indie games are so cool.
Also, unrelated, but I keep accidentally uploading the blog posts with the wrong number and then fixing it immediately, because for some reason, my local folder structure has lost a number somewhere.
What a bother…
Anyways, let’s jump into the update!
The last few weeks have been focused a lot on a combination of UI and overall polish. The refactor entered a playable state about a month and change ago, and we have since been reintroducing a lot of cool aesthetic stuff that had been missing.
We’ve also got a lot of brand new menus! If you have played any of my custom level packs before, you might recognize a few of these level names.
Last, but not least… we’ve been implementing a bunch of new mechanics you might have been anticipating. Here’s demos of switches and wind - though they’re very visually unfinished, they work!
Everything is finally starting to come together… I get emotional sometimes, thinking about it. We’ve come so far! I’m anticipating we’ll have polished up these mechanics by the next dev blog, and you’ll get to see them properly in action. We won’t spoil too much, though… there are a lot of levels we want to keep on the D.L. (which means ‘DOWN LOW’, by the way!!), and a lot of those levels are the ones featuring new mechanics.
Now, what kind of update would it be if we didn’t have a segment from our good friend, CraftedCart? Take it away.
State: A necessary part of programming, and while it would be nice to not have deal with managing too much state, it is very prevalent within game development.
So: for starters, let’s give some examples of what we might want to keep track of for each player in Rolled Out!
Now, I’m just gonna focus on the first six here (the ones beginning with “Whether”). One thing to note about these six is that one one of those things can be active at any given point in time (so, for example, you could have goaled, or you could be in the first intro cutscene, but you can’t be both at once) - we can create a state machine out of this!
Simply put, a state machine is just a box that has a certain state (eg: PlayerGameplay), and can be transitioned to different states. For a basic state machine, that’s it! If you wanted to, you can do fancier things like doing stuff when transitioning between states, having states store some data for themselves - those might be things we would want to look in to in the future if stuff starts getting out of hand, but for now we just have a super-barebones version where you can just switch state and that’s that.
Perhaps unsurprisingly, a barebones state machine has quite a simple implementation. For starters, we need some place to define all of our states…
1 | namespace EPlayerMode |
…yup, that’ll do. Then, we just need a variable to store the state, and some way to change the state stored.
1 | class ROCORE_API FRollingCtrl : public FBaseCtrl |
That’ll do it!
Now… why have a state machine in the first place? For… a long while in the earlier days of the game, managing state was.. a bit of a mess - mostly involving various boolean variables, or sometimes not bothering at all. For example, the way fallouts and goaling used to work is when you goaled, some code would run, and after a fixed delay some more code would run to restart the stage, or whisk you off to the next level. It was entirely possible for you to goal, and then fallout since… it never occurred to us to check that we had goaled before falling out.
Anyways, my point is having all these mutually exclusive states just in a single variable is way easier to manage over just tacking more and more stuff on over time. When we pass through the goal, we check that the state machine is in the PlayerGameplay
state before doing anything. That’s a lot easier to manage over checking stuff like not GoalCrossed and not FallOut and not TimeOver
- and if for some reason in the future we want to add another failure state, we don’t need to bother updating the goal checking code to say ... and not YouPickedUpSoManyCoinsThatYouGotTooHeavyAndPunchedAHoleInTheStage
.
You’ll be pleased to know that I found the image that I was looking for.
Thanks for reading, and see you in October.
]]>Thanks for watching, and we’ll see you on the 15th…. provided we’re not late again eehee.
]]>