Pet Project Platformer Mechanics

On my own time I’ve being trying to learn a little bit of webdev, and along the way I decided I want to write Typescript in Makecode again. I had a hiccup with not knowing arrays needed to be initialized, but now I have a pretty good demo platformer. I wished I could’ve gotten some sort of enemy in, but I I’ve felt I had invested quite already, and as it turned out the Sprite lifetime management issue was trivial compared to some other bugs i had to fix, and trying to keep scrolling through what ended up being over 500 lines of over engineered code to figure what went wrong was not fun to say to least.

The game is a small 2 level platformer with a little cyclops slime. It isn’t very fast but jumps quick and can slide up walls pretty well. The game has some other pretty neat stuff.

  • The game showcases my own custom decals sprites, or entities that are cosmetic. The trees, some of the rocks, the cave background, and the birds flying on the second level are all decals.
  • The game has a pretty consistent cartoonish look, although I do not love the cave background on the second level, I’m not sure how id go about implement something like a generic cave background that scales depending on tilemap and location.
  • Going through the portals also has a nice animation, which took me an embarrassing amount of time to pull off, and even then I’m not exactly happy with the implementation.

Also Some Questions I have for those more knowledge in Makecode API and Typescript.

  1. How is it possible to “pause the game” but still be able to draw animations and of the sort? to make my transition cut-scenes I had to make a lot of state flags which do not scale well and make the code more complicated.

  2. Is There A Hashmap/Dictionary In Makecode’s Typescript? Using arrays for everything worked out but it would be nice if there was a key value class.

  3. Is There A Get Current Tilemap Function? Not as big but was a little inconvenient

  4. So one of the times I shot myself in the foot was with this object

let arr: [Animator, boolean][] = [];

Its supposed to Be A Tuple Of An Animator And A Bool, Basically While I Was Figuring Out Playing Animations And Being Able To Signal When They are done, I made this as part of a larger namespace singleton to signal whether to autodestroy the id or not, or keep the id, possibly using leaking memory, to be able to access information about whether the animation had finished. So I had this code here.

export function Update() {
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] == undefined) continue;
            if (arr[i][0].Update() == true) {
                if (arr[i][1] == true) freeList.push(i);
                arr[i] = undefined;
            }
        }
    }

basically if the slot was valid, I would run the update function, which would also return a bool on whether the animation countdown finished, but for some reason, It just would not work. It was a pretty frustrating bug, as my Intellisense didn’t throw and error and knew the obj, but It just did not work.
Eventually
Anyways Heres The Link
|
v

Answers:

  1. There is a way to do this in TypeScript, but you have to be careful when using it. game.pushScene() will start a new scene on top of the current one and game.popScene() will remove it. See collapsed section below for a more detailed explanation
  2. Objects in JavaScript/TypeScript are equivalent to dictionaries in other languages. For example:
let myMap: {[index: string]: number} = {};

myMap["hello"] = 1;
myMap["goodbye"] = 243 * 1234
  1. To get the current Tilemap, you can use game.currentScene().tileMap or if you add the arcade-tile-util extension to your project you can use tileUtil.currentTilemap()
  2. Taking a look over your code, I think you might have a misunderstanding of how arrays in JavaScript work. See the second collapsed section below for a long explanation
Push/Pop scene

MakeCode Arcade has a concept called a Scene which represents the currently running game. All handlers from game.onUpdate(), controller.A.onEvent(), sprites.onOverlaps(), are stored on the scene and will not run while a new scene is pushed on top of the current one. Similarly, all Sprites are stored on the scene and will no longer draw to the screen or move until the top scene is removed.

Because of that second point, pushing a scene will immediately cause the screen to go black as the sprites have stopped drawing. If you want to avoid that, you need to re-enable rendering on the bottom scene by setting a flag. For example:

game.pushScene();
game.currentScene().flags |= scene.Flag.SeeThrough;

Another important point is how threading works in arcade. Threads do not get stopped from running when you push a scene onto the scene stack. For example, if you do this:

setTimeout(() => {
    scene.setBackgroundColor(3)
}, 500)
game.pushScene()

That setTimeout will still run when the new scene takes effect. That’s why it’s recommended that you always keep your game logic inside of game.onUpdate when possible.

Arrays

Arrays in JavaScript are more like linked lists than the arrays you are used to in C++.

First off, make sure you are using arr.push() when adding values to arrays instead of arr[arr.length] = 0. This is a place where MakeCode differs slightly from JavaScript in array semantics, but it’s generally good practice to use .push

In your code, I see that you are keeping a list of free indices in your animator arrays and then replacing them when you create new ones. This is not going to get you much of a performance increase, instead, I would do something like this:

    export function Update() {
        let didRemove = false;
        for (let i = 0; i < arr.length; i++) {
            let [an, b] = arr[i];
            if (an.Update() == true) {
                arr[i] = undefined;
                didRemove = true;
            }
        }

        // If we removed an item, filter the list to remove all undefined entries
        if (didRemove) {
            arr = arr.filter(a => !!a);
        }
    }

This is a much more JavaScript-y way of doing things

Btw I bypassed the issue with structured binding I just accidentally hit send.

export function Update() {
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] == undefined) continue;
            let [an, b] = arr[i];
            if (an.Update() == true) {
                if (b == true) freeList.push(i);
                arr[i] = undefined;
            }
        }
    }