Dev Blog 30 - We Missed One Lol

Some of the most astute among you have noticed that we didn’t have a dev blog update in the beginning of November.

…sorry, I was shaving.

aerodynamic

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.

new material settings

material types

With clever use of these material types, combined with the right textures, you can pull off some neat looking stuff.

in blender

in engine

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.

gate floor

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.

scrape marks

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!


The UI navigation kludge

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.

Idea 1: Pull a Tom Scott

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…

Idea 2: Add our own custom keys, and simulate those key presses

…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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
UCLASS(BlueprintType)
class ROGAME_API UInputManager : public UObject
{
GENERATED_BODY()

public:
// Custom keys for menu-ing
static const FKey KeyPolarLeft;
static const FKey KeyPolarRight;
static const FKey KeyPolarUp;
static const FKey KeyPolarDown;
static const FKey KeyPolarAccept;
static const FKey KeyPolarBack;
static const FKey KeyPolarPrevPage;
static const FKey KeyPolarNextPage;

// --snip--
};

…then we make a custom navigation config that says UI navigation should respond to these keys we just made…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class FPolarNavConfig : public FNavigationConfig
{
public:
FPolarNavConfig()
{
bTabNavigation = false;
bKeyNavigation = true;
bAnalogNavigation = false;

KeyEventRules.Add(UInputManager::KeyPolarLeft, EUINavigation::Left);
KeyEventRules.Add(UInputManager::KeyPolarRight, EUINavigation::Right);
KeyEventRules.Add(UInputManager::KeyPolarUp, EUINavigation::Up);
KeyEventRules.Add(UInputManager::KeyPolarDown, EUINavigation::Down);
}

virtual EUINavigation GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent) override
{
return EUINavigation::Invalid;
}

virtual EUINavigationAction GetNavigationActionForKey(const FKey& InKey) const override
{
if (InKey == UInputManager::KeyPolarAccept) return EUINavigationAction::Accept;
if (InKey == UInputManager::KeyPolarBack) return EUINavigationAction::Back;
return EUINavigationAction::Invalid;
}
};

…and finally, we register the keys and config on game startup…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void UInputManager::Init()
{
InitBindings();

// Define custom keys
static const FName MENU_CAT = TEXT("PolarKeys");
EKeys::AddKey(FKeyDetails(KeyPolarLeft, INVTEXT("PolarLeft"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarRight, INVTEXT("PolarRight"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarUp, INVTEXT("PolarUp"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarDown, INVTEXT("PolarDown"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarAccept, INVTEXT("PolarAccept"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarBack, INVTEXT("PolarBack"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarPrevPage, INVTEXT("PolarPrevPage"), 0, MENU_CAT));
EKeys::AddKey(FKeyDetails(KeyPolarNextPage, INVTEXT("PolarNextPage"), 0, MENU_CAT));

// Don't use UE4's stock navigation mappings at all
FSlateApplication::Get().SetNavigationConfig(MakeShared<FPolarNavConfig>());
// FSlateApplication::Get().SetNavigationConfig(MakeShared<FNullNavigationConfig>());
}

…oh and, don’t forget to unregister the keys when the game ends.

1
2
3
4
5
6
void UInputManager::OnEnd()
{
EKeys::RemoveKeysWithCategory(TEXT("PolarKeys"));

PolarInput::TearDown();
}

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!

Idea 3: Force UI navigation myself, instead of trying to trigger it through key presses

…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
2
3
TSharedPtr<SWidget> FocusedWidget = App.GetUserFocusedWidget(0);
if (!FocusedWidget) FocusedWidget = App.GetKeyboardFocusedWidget();
if (!FocusedWidget) return;

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…

  • The widget underneath the mouse cursor, which we don’t care about do I give it null
  • A mouse event, which we also don’t care about so I give it null again
  • And a user ID, which I just say is 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.