How is MakeCode Arcade's controller API designed under the hood? I want to learn low-level core engine design

I already know how to make games in MakeCode Arcade using controller.moveSprite() and other high-level APIs. But now I want to go deeper — I’m studying how the core logic behind the controller and sprite systems actually works.

How did the MakeCode team (like Richard) design those APIs from scratch? I’m trying to understand how they implemented the low-level engine code that powers controller.ts, not just how to use it.

Here’s the file I’m studying:
pxt-common-packages/libs/game/controller.ts

If anyone from the team or community can explain the design process, structure, or give insight into how they coded those APIs to escape the limits of game scripting, I’d really appreciate it!

2 Likes

MakeCode has something called an event bus that we use for propagating events through the game engine. This is a concept that is common to all of our editors, not just arcade.

Basically, you can think of the event bus as a way for different parts of the code base to talk to each other. Any piece of code can raise an event on the event bus which causes all code that has subscribed to that event to be triggered. Every event that’s raised on the bus has two values: an event id and a component id. The event id is just an arbitrary number unique to that event, and the component id is an id number that represents the hardware component that generated the event (e.g. the A button, the Up button, etc.)

All of the hardware inputs in Arcade are implemented using the event bus. We write some C++ code that pushes the events on to the event bus and some TypeScript code that then subscribes to those events and implements the actual APIs that you reference in your user project.

That’s all a little abstract, so let’s look at an example of what happens when a button is pressed.

When the C++ code detects that the button has been pressed, it fires the INTERNAL_KEY_DOWN event on the event bus. This has an event id of 2051. The A Button in arcade has a component id of 5. So, when the A button is pressed the event fired on the event bus will look like this:

eventId: 2051, componentId: 5

Inside of controllerOverrides.ts we create a Button object for the A button. This is the controller.A object that you may have seen if you’ve ever looked at the TypeScript in an arcade project. That Button object subscribes to the INTERNAL_KEY_DOWN event in controllerButton.ts and updates the state when the event comes in on the event bus.

Again, I get that this is a little complicated, so let’s try an experiment to make things a little clearer. Any piece of code can fire events on the event bus, not just C++, so what would happen if we wrote some code that fired an event with event id 2051 and component id 5? Try pasting this code into an empty project:

controller.A.onEvent(ControllerButtonEvent.Pressed, () => {
    scene.setBackgroundColor(3)
})
control.raiseEvent(2051, 5)

If you run that, you’ll see that the A button event triggers without you actually pressing the button!

2 Likes

Just to clarify — I already understand how to use controller.A.onEvent() and control.raiseEvent() — I’ve written my own handlers and triggered custom events using those before.

What I’m actually trying to learn is how and why you originally designed this system in controller.ts.

  • How did you decide to structure controller input using control.raiseEvent and ControllerButtonEvent?

  • What connects controller hardware logic to sprite movement under the hood?

  • How does it all flow — from input hardware to event system to sprite motion?

I’m not trying to figure out how to use these APIs — I’m trying to learn how to build them like you did.

Thanks again for your time — I know this is deep stuff, but it’s helping me learn engine-level thinking.

3 Likes

If you want to watch the engineers build things from scratch in real time (and watch them discuss and explain their logic), then watch them on the MakeCode Advanced stream.

The simple answer: It takes experience, building a lot of projects and learning from your past mistakes.

2 Likes

to be honest I am not wanting to make games dragging them to the on start, I want to know how you are up to 300+ lines, it feels super hard to avoid the high level functions I know low level stuff but it just feels so hard what I need to make a engine structured engine to learn how they made the prompt file on https://github.com/microsoft/pxt-common-packages/blob/master/libs/game/prompt.ts. If you can may you please help how you know it is not off topic I know this is weird because when I do my functions are off topic, but in game logic I know what I am doing because I just call them, just like what beginners or game jammers do. I want to just understand how we can call the move sprite or ask for string behind the hood, and also what is the helpers._promptForText do, I think it is a private or internal function meant for engine devs, I a ready.