microsoft / pxt-arcade

Arcade game editor based on Microsoft MakeCode
https://arcade.makecode.com
MIT License
484 stars 210 forks source link

Sprite.forever? #757

Open jamesadevine opened 5 years ago

jamesadevine commented 5 years ago

I'm always frustrated when I have to write a game loop, it seems very 1980s. Given the asynchronicity of the language I think it would be much cooler to have a forever loop for each instance of a sprite.

For instance, here's a little excerpt from a game I was playing with:

// baddie movement
game.onUpdateInterval(30, function () {
    ticks += 1
    console.log("T")
    if (baddie.x < queen.x) {
        baddie.vx = 20
        if (ticks > 4) {
            baddie.setImage(baddieRight)
        }
    } else {
        baddie.vx = -20
        if (ticks > 4) {
            baddie.setImage(baddieLeft)
        }
    }
    if (baddie.y < queen.y) {
        baddie.y++;
    } else if (baddie.y > queen.y) {
        baddie.y--;
    }
    if (ticks == 60) {
        ticks = 0
        sprites.createProjectileFromSprite(baddieProjectile, baddie, 4 * baddie.vx, 0)
        baddie.setImage((baddie.vx) ? baddieRightThrow : baddieLeftThrow)
    }
})

Note the comment above: this code is specific to the baddie sprite. Ideally I want this code to run every time there's a baddie created.

When really the code above should "belong" to a sprite and look a little something like this:

// baddie movement
baddie.forever(30, function () {
    ticks += 1
    .....
    this.x++;
    .....
})
jwunderl commented 5 years ago

I like the idea of the event, just a note that sprites.allOfKind can be used in cases like that

// baddie movement
game.onUpdateInterval(30, function () {
    sprites.allOfKind(SpriteKind.Baddie).forEach(baddie => {
        ticks += 1
        console.log("T")
        if (baddie.x < queen.x) {
            baddie.vx = 20
            if (ticks > 4) {
                baddie.setImage(baddieRight)
            }
        } else {
            baddie.vx = -20
            if (ticks > 4) {
                baddie.setImage(baddieLeft)
            }
        }
        if (baddie.y < queen.y) {
            baddie.y++;
        } else if (baddie.y > queen.y) {
            baddie.y--;
        }
        if (ticks == 60) {
            ticks = 0
            sprites.createProjectileFromSprite(baddieProjectile, baddie, 4 * baddie.vx, 0)
            baddie.setImage((baddie.vx) ? baddieRightThrow : baddieLeftThrow)
        }
    })
})
jamesadevine commented 5 years ago

Ah, but I have two separate type of movement: (1) the baddie which hones in on the queen "automatically"; and (2) the control of the queen. I actually have two separate game interval loops, here's the second:

// queen movement
game.onUpdateInterval(30, function () {
    if (controller.up.isPressed()) {
        direction = PlayerDirection.Forward
        queen.setImage(queenUp)
    }
    if (controller.down.isPressed()) {
        direction = PlayerDirection.Back
        queen.setImage(queenDown)
    }
    canMove = true
    if (queen.overlapsWith(box)) {
        if (queen.x < box.x) {
            box.x++;
        } else if (queen.x > box.x) {
            box.x--;
        } else {
            canMove = false
        }
    }
    if (canMove) {
        if (controller.left.isPressed()) {
            direction = PlayerDirection.Left;
            queen.setImage(queenLeft)
            queen.x -= 2
        }
        if (controller.right.isPressed()) {
            direction = PlayerDirection.Right;
            queen.setImage(queenRight)
            queen.x += 2
        }
    }
    queen.vy += gravity
})
jamesadevine commented 5 years ago

The point being that I hate non-modular code, and chucking it all into one update loops is very non-modular :wink:

riknoll commented 5 years ago

We used to have sprite.onUpdate() a long time ago but I think it got lost in our may API redesigns.

jwunderl commented 5 years ago

Yeah, makes sense; can use functions but it's a common use case. so basically

arcade-screenshot 1

to make

2019-02-13 16 05 08

abchatra commented 5 years ago

This API can only be in Typescript. It is advanced in my opinion and doesn't need to be in blocks.

jamesadevine commented 5 years ago

I suggest that instead of:

on update "Enemy" sprites every 30 ms (Sprite)

You simply add an iterator for sprites that plugs into the existing loops block:

for element (sprite) of <enemy sprites>
screenshot 2019-02-26 at 15 42 44

Then the user adds the update every X ms if required...

It's currently not clear that you can iterate by sprite Kind at all.