[BUG] Occasionally, Sprite Animation Plays Wrong Frames

Hello MakeCode Forum!

I was having a weird visual bug in my game’s code where my sprite’s animation was not being displayed as I wanted it to. The intended effect is that the walking animation will stop once the action button is pressed (A), then play the swing animation. However, I found it to also play some additional frames of the walking animation.

The extension I’m using for the animation handling is called “Animation”, which gives advanced functionality to the animation toolset. I also use sprite data to help with sprite-specific, like to find out what direction the character is facing

Below is the JavaScript code that is relevant for the animation, any help will be appreciated!

AttachFunctionToKeys(ACTION_KEYS, function () { // Function when A (or another action key) is pressed

   if (!sprites.readDataBoolean(usr, "attacking")) { // Only run if the sprite wasn't attacking

       sprites.setDataBoolean(usr, "attacking", true)
        /**
        INTENDED BEHAVIOR:
        - Sprite stops current walking animation
        - The attack animation plays
        - Sprite can return to walking animation after a set amount of time
        ACTUAL BEHAVIOR:
        - Occassionally, the sprite will 
        */
       animation.stopAnimation(animation.AnimationTypes.All, usr) 

       pause(50)

       animation.setAction(usr, sprites.readDataNumber(usr, "action") + 4) // +4 represents the offset the swing animation compared to the walking animation, as it is set second

        /** Later in the code, once the appropriate sword picture is set */

       pause(1000) // Lets swing animation play

       animation.setAction(usr, sprites.readDataNumber(usr, "action")) // Resumes proper walking animation

       sprites.setDataBoolean(usr, "attacking", false) // Reset attack variable

   }

})
...
game.onUpdate(function(){

   /** Code for moving sprite... Then: */
   // Sets a variable for the user sprite, dependant on what key is being pressed

   if (!sprites.readDataBoolean(usr, "attacking")) // Do not update facing direction once swinging

   { // Otherwise, set facing direction based on what direction button is pressed (functions return boolean value)

       if (IsLeftPressed()) {

           sprites.setDataNumber(usr, "action", 2) // Move Left

       } else if (IsRightPressed()) {

           sprites.setDataNumber(usr, "action", 1) // Move/Swing Right

       }

       else if (IsUpPressed()) {

           sprites.setDataNumber(usr, "action", 3) // Move up

       } else if (IsDownPressed()) {

           sprites.setDataNumber(usr, "action", 0) // Move down

       }

   }

   

   // Checks if the player is actually moving
   // The sprite hitbox is to help determine collisions, not actually animated
   if (!sprites.readDataBoolean(usr, "attacking") && Math.abs(usr_hitbox.vx) + Math.abs(usr_hitbox.vy) > 0)

       animation.setAction(usr, sprites.readDataNumber(usr, "action")) // If it is, play animation

   else if (!sprites.readDataBoolean(usr, "attacking")) // If the sprite is not attacking

       animation.stopAnimation(animation.AnimationTypes.ImageAnimation, usr) // Otherwise, stop animation

/**Some more code*/
})

I can provide video of the visual bug occurring, but I’m currently going to be busy with other things for a bit before I can provide that. Hope this is enough!

3 Likes

Bump! Video of the Bug Occurring

2026-08-1116-13-16-ezgif.com-video-to-gif-converter
I tried adding an idle animation that has a framerate of 0ms (It basically stops what I thought was going on), but I think there is a different bug that occurred, and it was the swing animation :sob:
My theory is that the animation will mark when it ends, and resumes there when the action returns to the swing state (I want it to restart the swing animation). I’ll try something else, but feel free to provide advice

It waits on the inputs I’m pretty sure so spamming willl wait until the pause is finished then play the animation again use the timer extension to stop this

More specifically, the [At most every 500ms] block!

1 Like

If you could post the games code that would be very appreciated

actually the [on [A] button [pressed]] also does it, since it is its own thread (It’s also really hard to tell). I call it buffering but it’s probably a different term.

/**
 * CREDITS!!
 * Jam : Music assets
 * MisterMike : Link NES sprites for animation reference
 */

namespace SpriteKind {
    export let Hitbox = SpriteKind.create()
}
interface Song_Dictionary<T>
{
    [key : string] : T;
}
// Game Vars and Counters
let PLAYER_SPEED = 100
let WALK_ANIM_TIME = 200
let RUNNING = false
let player_a_c = 0
let BACKGROUND : Image
let SONGS : Song_Dictionary<music.Playable[]> = {
    "overworld" : [music.createSong(assets.song`field`)],
    "dungeon" : [music.createSong(assets.song`dungeon`)]
}
let ACTION_KEYS : browserEvents.KeyButton[] = [browserEvents.Z, browserEvents.Space]
// -- Movement calc
let v_a : number[]
let f_x : number
let f_y : number
let mag : number
let anim : animation.Animation
// Camera calc
let bx : number
let by : number
// Screen follows 4:3 ratio
let cam_a_x : number = 64
let cam_a_y : number = 48
let cam_c : number = 40
let move_cam : boolean = false
// Timer calc
let s_time : number
let d_time : number
// Music Logic
let playing_song : boolean = false
// Functions for game
function StartSong(name: string)
{
    if (SONGS[name] != null)
    {
        playing_song = true
        timer.background(function(){
            while (playing_song)
            {
                for (let i = 0; i < SONGS[name].length; i++)
                {
                    if (playing_song) SONGS[name][i].play(music.PlaybackMode.UntilDone)
                }
            }
        })
    }
}
function BoolToInt(b: boolean) // Converts Boolean to Integer
{
    if (b)
    {
        return 1;
    }
    return 0;
}
function ShiftCamera(dx: number, dy: number)
{
    move_cam = true
    /**
     * Moves camera using the function f(x)=16ln(x), where x is delta time, until f(x) >= dx or f(y) >= dy
     * If either dx or dy is negative, f(x) will be flipped
     */
    timer.background(function(){
        bx = scene.cameraProperty(CameraProperty.X);
        by = scene.cameraProperty(CameraProperty.Y);
        s_time = game.runtime()
        while (Math.abs(scene.cameraProperty(CameraProperty.X) - bx) < Math.abs(dx) || Math.abs(scene.cameraProperty(CameraProperty.Y) - by) < Math.abs(dy)) {
            d_time = (game.runtime() - s_time) / 1000.0
            if (dx >= 0 && dy >= 0) {
                scene.centerCameraAt(bx + cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx > 0), by + cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy > 0))
            } else if (dx <= 0 && dy >= 0) {
                scene.centerCameraAt(bx - cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx < 0), by + cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy > 0))
            } else if (dx >= 0 && dy <= 0) {
                scene.centerCameraAt(bx + cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx > 0), by - cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy < 0))
            } else {
                scene.centerCameraAt(bx - cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx < 0), by - cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy < 0))
            }
            pause(10)
            console.log(scene.cameraProperty(CameraProperty.X) - bx)
        }
        scene.centerCameraAt(bx + dx, by + dy)
        move_cam = false
    })
}
function SetBGImage(img: Image)
{
    BACKGROUND = img
    scene.setBackgroundImage(BACKGROUND)
}
function SetBGColor(color: number)
{
    BACKGROUND = image.create(160, 120)
    BACKGROUND.fill(color)
    scene.setBackgroundImage(BACKGROUND)
}

// Math functions
function GetMagnitude(x: number, y: number)
{
    return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
}

function NormalizeJoystick(speed: number, u: boolean, l: boolean, d: boolean, r: boolean)
{
    f_x = BoolToInt(r) - BoolToInt(l)
    f_y = BoolToInt(d) - BoolToInt(u)
    mag = GetMagnitude(f_x, f_y)
    
    return [speed * (f_x / mag), speed * (f_y / mag)]
}

// Finally I can do some better things
function attachAnim(spr: Sprite, img_anim: Array<Image>, fpms: number, flipped: boolean)
{
    anim = animation.createAnimation(player_a_c, fpms)
    for (let i = 0; i < img_anim.length; i++)
    {
        if (flipped)
        {
            img_anim[i].flipX()
            anim.addAnimationFrame(img_anim[i])
        }
        else
            anim.addAnimationFrame(img_anim[i])
    }
    animation.attachAnimation(spr, anim)
    player_a_c++ // Incremeant for the next player animation
}

// Key Pres Logic Simplification
function IsRightPressed()
{
    return browserEvents.D.isPressed() || browserEvents.ArrowRight.isPressed()
}
function IsLeftPressed()
{
    return browserEvents.A.isPressed() || browserEvents.ArrowLeft.isPressed()
}
function IsUpPressed()
{
    return browserEvents.W.isPressed() || browserEvents.ArrowUp.isPressed()
}
function IsDownPressed()
{
    return browserEvents.S.isPressed() || browserEvents.ArrowDown.isPressed()
}
function IsActionPressed()
{
    for (let i = 0; i < ACTION_KEYS.length; i++)
    {
        if (ACTION_KEYS[i].isPressed()) return true;
    }
    return false;
}
function AttachFunctionToKeys(keys: browserEvents.KeyButton[], cb: () => void)
{
    for (let i = 0; i < keys.length; i++)
    {
        keys[i].onEvent(browserEvents.KeyEvent.Pressed, cb)
    }
}
// -- Title Screen
SetBGImage(assets.image`title`)
pauseUntil(IsActionPressed)

// -- Game initialization
//scene.setBackgroundImage(assets.image`sky`)
SetBGColor(7)
let usr = sprites.create(assets.image`wiz_front`, SpriteKind.Player)
// Makes a hitbox for the player sprite
let usr_hitbox = sprites.create(assets.image`wiz_hitbox`, SpriteKind.Hitbox)
usr_hitbox.setFlag(SpriteFlag.Invisible, true)
// Sword sprite
let sword = sprites.create(assets.image`sword_up`, SpriteKind.Projectile)
sword.setFlag(SpriteFlag.Invisible, true)
// Sprite variables (will be used with other sprites, likely)
sprites.setDataNumber(usr, "action", 0)
sprites.setDataBoolean(usr, "attacking", false)
attachAnim(usr, [assets.image`wiz_walk_d_1`], 0, false) // Idle down (0)
attachAnim(usr, [assets.image`wiz_walk_r_1`], 0, false)
attachAnim(usr, [assets.image`wiz_walk_r_1`], 0, true)
attachAnim(usr, [assets.image`wiz_walk_u_1`], 0, false)
attachAnim(usr, [ // Down Walk Animation (4)
    assets.image`wiz_walk_d_1`,
    assets.image`wiz_walk_d_2`
], WALK_ANIM_TIME, false) 
attachAnim(usr, [ // Right Walk Animation (5)
    assets.image`wiz_walk_r_1`,
    assets.image`wiz_walk_r_2`
], WALK_ANIM_TIME, false)
attachAnim(usr, [ // Left Walk Animation (6)
    assets.image`wiz_walk_r_1`,
    assets.image`wiz_walk_r_2`
], WALK_ANIM_TIME, true)
attachAnim(usr, [ // Up Walk Animation (7)
    assets.image`wiz_walk_u_1`,
    assets.image`wiz_walk_up_2`
], WALK_ANIM_TIME, false)
/**
 * SWORD ANIMATION
 */
attachAnim(usr, [ // Down Swing Animation (8)
    img`
        . . . e e . 8 8 8 8 8 . . . . .
        . . e b e 8 8 8 8 8 8 8 . . . .
        . e e b 8 8 1 1 1 1 8 8 8 . . .
        e e b b 8 1 1 1 1 1 1 8 8 . b .
        e e b b 1 b c b b c 1 1 8 b b .
        4 e e b 1 b c b b c b 1 8 b . .
        . 4 e e 8 b b c c b b b b . . .
        . . 4 e 8 8 b c c b b 6 6 . . .
        . . . 4 8 8 8 b b b 6 6 6 6 . .
        . . . . e 8 8 8 8 8 6 6 6 6 . .
        . . . . 8 e 8 e e 6 6 6 6 8 . .
        . . . . 8 8 e e 8 6 6 6 8 8 . .
        . . . e e 8 8 e e b b 6 8 8 e .
        . . e e e . 8 8 b b b 8 8 e e e
        . . . . . . . . b b b . e e e e
        . . . . . . . . . . . . . . . .
    `,
    img`
        . . . e e . 8 8 8 8 8 . . . . .
        . . e b e 8 8 8 8 8 8 8 . . . .
        . e e b 8 8 1 1 1 1 8 8 8 . . .
        e e b b 8 1 1 1 1 1 1 8 8 . b .
        e e b b 1 b c b b c 1 1 8 b b .
        4 e e b 1 b c b b c b 1 8 b . .
        . 4 e e 8 b b c c b b b b . . .
        . . 4 e 8 8 b c c b b 6 6 . . .
        . . . 4 8 8 8 b b b 6 6 6 6 . .
        . . . . e 8 8 8 8 8 6 6 6 6 . .
        . . . . 8 e 8 e e 6 6 6 6 8 . .
        . . . . 8 8 e e 8 6 6 6 8 8 . .
        . . . e e 8 8 e e b b 6 8 8 e .
        . . e e e . 8 8 b b b 8 8 e e e
        . . . . . . . . b b b . e e e e
        . . . . . . . . . . . . . . . .
    `,
    img`
. . . . . 8 8 8 8 8 8 . . . . . 
. . . . 8 8 8 8 8 8 8 8 . . . . 
. . b . 8 1 1 1 1 1 1 8 . b . . 
. . b . 1 1 1 1 1 1 1 1 . b . . 
. . b b 1 b c b b c b 1 b b . . 
. . b b 1 b c b b c b 1 b b . . 
. . . b b b b b b b b b b 6 . . 
. . . 8 8 b b c c b b 8 8 6 . . 
. e e e e e b b b b 8 8 6 6 6 . 
e e 4 e e e e 8 8 8 8 8 b 6 6 . 
e 4 4 4 e e 4 e e 8 8 b b b 6 . 
e e 4 e e e 4 8 e e e e b b b . 
e e 4 e e e 4 e e 8 8 8 8 b . . 
e e e e e e 4 8 8 8 8 8 . . . . 
. 4 4 4 4 4 e . . e e e . . . . 
. . . . e e e . . . . . . . . . 
    `,
    img`
        . . . . . 8 8 8 8 8 8 . . . . .
        . . . . 8 8 8 8 8 8 8 8 . . . .
        . . b . 8 1 1 1 1 1 1 8 . b . .
        . . b . 1 1 1 1 1 1 1 1 . b . .
        . . b b 1 b c b b c b 1 b b . .
        . . b b 1 b c b b c b 1 b b . .
        . . . b b b b b b b b b b 6 . .
        . . . . 8 b b c c b b 8 6 6 . .
        . . e e e e e b b b 8 8 8 b . .
        . e e 4 e e e e 8 8 8 8 8 b . .
        . e 4 4 4 e e 4 e e 8 8 e . . .
        . e e 4 e e e 4 8 e e e 8 . . .
        . e e 4 e e e 4 e e 8 8 8 . . .
        . e e e e e e 4 8 8 8 e . . . .
        . . 4 4 4 4 4 . . e e e . . . .
        . . . . . . . . . e e e . . . .
`], 250, false)
attachAnim(usr, [ // Right Swing Animation (9)
    img`
        . . . . . . . . . . . . . . . .
        . . . . . . 8 8 8 8 . . . . . .
        . . . . 8 8 8 8 8 1 1 1 1 . . .
        . . . 8 8 b 8 8 1 1 1 1 1 1 . .
        . . . 8 8 b b 1 1 1 1 1 1 . . .
        . . 8 8 8 b b b 1 b b c b . . .
        . 8 8 8 1 1 b b 1 b b c b b b .
        . 8 . . 1 1 1 b b b b b b . . .
        . . . . 8 8 8 8 8 b b b b b . .
        . . . 8 8 6 6 6 6 6 6 b b b . .
        . . . 8 6 6 6 6 6 6 6 b b . . .
        . . 8 8 6 6 6 6 6 6 6 8 . . . .
        . 8 8 8 8 6 6 6 8 8 8 e e . . .
        e e 8 8 8 8 8 8 e e e e e . . .
        e e e 8 8 8 8 8 8 8 8 8 e e . .
        . e e e . . . . . . . e e e e .
    `,
    img`
        . . . . . . . . . . . . . . . .
        . . . . . . 8 8 8 8 . . . . . .
        . . . . 8 8 8 8 8 1 1 1 1 . . .
        . . . 8 8 b 8 8 1 1 1 1 1 1 . .
        . . . 8 8 b b 1 1 1 1 1 1 . . .
        . . 8 8 8 b b b 1 b b c b . . .
        . 8 8 8 1 1 b b 1 b b c b b b .
        . 8 . . 1 1 1 b b b b b b . . .
        . . . . 8 8 8 8 8 b b b b b . .
        . . . 8 8 6 6 6 6 6 6 b b b . .
        . . . 8 6 6 6 6 6 6 6 b b . . .
        . . 8 8 6 6 6 6 6 6 6 8 . . . .
        . 8 8 8 8 6 6 6 8 8 8 e e . . .
        e e 8 8 8 8 8 8 e e e e e . . .
        e e e 8 8 8 8 8 8 8 8 8 e e . .
        . e e e . . . . . . . e e e e .
    `,
    assets.image`wiz_walk_r_2`,
    assets.image`wiz_walk_r_1`
], 250, false)
attachAnim(usr, [ // Left Swing Animation (10)
    img`
        . . . . . . . . . . . . . . . .
        . . . . . . 8 8 8 8 . . . . . .
        . . . . 8 8 8 8 8 1 1 1 1 . . .
        . . . 8 8 b 8 8 1 1 1 1 1 1 . .
        . . . 8 8 b b 1 1 1 1 1 1 . . .
        . . 8 8 8 b b b 1 b b c b . . .
        . 8 8 8 1 1 b b 1 b b c b b b .
        . 8 . . 1 1 1 b b b b b b . . .
        . . . . 8 8 8 8 8 b b b b b . .
        . . . 8 8 6 6 6 6 6 6 b b b . .
        . . . 8 6 6 6 6 6 6 6 b b . . .
        . . 8 8 6 6 6 6 6 6 6 8 . . . .
        . 8 8 8 8 6 6 6 8 8 8 e e . . .
        e e 8 8 8 8 8 8 e e e e e . . .
        e e e 8 8 8 8 8 8 8 8 8 e e . .
        . e e e . . . . . . . e e e e .
    `,
    img`
        . . . . . . . . . . . . . . . .
        . . . . . . 8 8 8 8 . . . . . .
        . . . . 8 8 8 8 8 1 1 1 1 . . .
        . . . 8 8 b 8 8 1 1 1 1 1 1 . .
        . . . 8 8 b b 1 1 1 1 1 1 . . .
        . . 8 8 8 b b b 1 b b c b . . .
        . 8 8 8 1 1 b b 1 b b c b b b .
        . 8 . . 1 1 1 b b b b b b . . .
        . . . . 8 8 8 8 8 b b b b b . .
        . . . 8 8 6 6 6 6 6 6 b b b . .
        . . . 8 6 6 6 6 6 6 6 b b . . .
        . . 8 8 6 6 6 6 6 6 6 8 . . . .
        . 8 8 8 8 6 6 6 8 8 8 e e . . .
        e e 8 8 8 8 8 8 e e e e e . . .
        e e e 8 8 8 8 8 8 8 8 8 e e . .
        . e e e . . . . . . . e e e e .
    `,
    assets.image`wiz_walk_r_2`,
    assets.image`wiz_walk_r_1`
], 250, true)

// attachAnim(usr, [], 250, false)
animation.setAction(usr, 1)
tiles.setCurrentTilemap(assets.tilemap`testing`)
scene.centerCameraAt(80, 60)
StartSong("overworld")
/**
 * ATTACKING LOGIC
 */
AttachFunctionToKeys(ACTION_KEYS, function () { // Lets the game know what
    if (!sprites.readDataBoolean(usr, "attacking")) {
        sprites.setDataBoolean(usr, "attacking", true)
        animation.setAction(usr, sprites.readDataNumber(usr, "action"))
        pause(50)
        animation.setAction(usr, sprites.readDataNumber(usr, "action") + 8) // If it is, play animation
        switch (sprites.readDataNumber(usr, "action"))
        {
            case 0:
                sword.setImage(assets.image`sword_down`)
                break;
            case 1:
                sword.setImage(assets.image`sword_right`)
                break;
            case 2:
                sword.setImage(assets.image`sword_left`)
                break;
            case 3:
                sword.setImage(assets.image`sword_up`)
                break;
        }
        pause(1000)
        animation.setAction(usr, sprites.readDataNumber(usr, "action"))
        sprites.setDataBoolean(usr, "attacking", false)
    }
})
// Player Movement & Animation
game.onUpdate(function(){
    v_a = NormalizeJoystick(PLAYER_SPEED * BoolToInt(!sprites.readDataBoolean(usr, "attacking")), IsUpPressed(), IsLeftPressed(), IsDownPressed(), IsRightPressed())
    usr_hitbox.setVelocity(v_a[0], v_a[1])
    usr.setPosition(usr_hitbox.x, usr_hitbox.y)
    // Sets a variable for the user sprite, dependant on what key is being pressed
    if (!sprites.readDataBoolean(usr, "attacking")) // Do not update facing direction once swinging
    {
        if (IsLeftPressed()) {
            sprites.setDataNumber(usr, "action", 2) // Move Left
        } else if (IsRightPressed()) {
            sprites.setDataNumber(usr, "action", 1) // Move/Swing Right
        }
        else if (IsUpPressed()) {
            sprites.setDataNumber(usr, "action", 3) // Move up
        } else if (IsDownPressed()) {
            sprites.setDataNumber(usr, "action", 0) // Move down
        }
    }
    
    // Checks if the player is actually moving
    if (!sprites.readDataBoolean(usr, "attacking") && Math.abs(usr_hitbox.vx) + Math.abs(usr_hitbox.vy) > 0)
        animation.setAction(usr, sprites.readDataNumber(usr, "action") + 4) // If it is, play animation
    else if (!sprites.readDataBoolean(usr, "attacking"))
        animation.setAction(usr, sprites.readDataNumber(usr, "action")) // Otherwise, stop animation
    // Camera
    if (!move_cam)
    {
        if (usr_hitbox.top > scene.cameraProperty(CameraProperty.Bottom))
            ShiftCamera(0, 114)
        else if (usr_hitbox.bottom < scene.cameraProperty(CameraProperty.Top))
            ShiftCamera(0, -114)
        else if (usr_hitbox.left > scene.cameraProperty(CameraProperty.Right))
            ShiftCamera(144, 0)
        else if (usr_hitbox.right < scene.cameraProperty(CameraProperty.Left))
            ShiftCamera(-144, 0)
    }
})

Here’s the whole code, although I don’t believe a lot of this is necessary to understand the problem. I will try the things @xInsqne1 and you said with the “at most every Xms” block, but I might also try doing something else that I might find to work. The idle animation code is included, as an attempted fix at the problem (each animation the character has is offset by four)

1 Like

What he said

1 Like

Oooo yeah that’s true! My function AttachFunctionToKeys(keys, cb) works similarly. I might try a KISS (Keep it Simple and Stupid) solution first tho, I’ll let you know how that goes!

I am Proud to Announce…

I have fixed the bug. It was, in fact, a KISS solution

/**
 * CREDITS!!
 * Jam : Music assets
 * MisterMike : Link NES sprites for animation reference
 */

namespace SpriteKind {
    export let Hitbox = SpriteKind.create()
}
interface Song_Dictionary<T>
{
    [key : string] : T;
}
function flipImgArr(img_arr: Array<Image>) {
    let out: Array<Image> = []
    for (let i = 0; i < img_arr.length; i++) {
        img_arr[i].flipX()
        out.push(img_arr[i])
    }
    return out
}

// Game Vars and Counters
let PLAYER_SPEED = 100
// -- Wiz Variables
let WALK_ANIM_TIME = 200

let SWING_ANIMATIONS: Array<Array<Image>> = [
/**  DOWN  **/ [assets.image`wiz_swing_d`, assets.image`wiz_swing_d`, assets.image`wiz_walk_d_1`, assets.image`wiz_walk_d_2`],
/**  RIGHT **/ [assets.image`wiz_swing_r`, assets.image`wiz_swing_r`, assets.image`wiz_walk_r_2`, assets.image`wiz_walk_r_1`],
/**  LEFT  **/ flipImgArr([assets.image`wiz_swing_r`, assets.image`wiz_swing_r`, assets.image`wiz_walk_r_2`, assets.image`wiz_walk_r_1`]),
/**   UP   **/ [assets.image`wiz_swing_u`, assets.image`wiz_swing_u`, assets.image`wiz_walk_up_2`, assets.image`wiz_walk_u_1`]
]
let RUNNING = false
let player_a_c = 0
// -- Misc...
let BACKGROUND : Image
let SONGS : Song_Dictionary<music.Playable[]> = {
    "overworld" : [music.createSong(assets.song`field`)],
    "dungeon" : [music.createSong(assets.song`dungeon`)]
}
let ACTION_KEYS : browserEvents.KeyButton[] = [browserEvents.Z, browserEvents.Space]
// -- Movement calc
let v_a : number[]
let f_x : number
let f_y : number
let mag : number
let anim : animation.Animation
// Camera calc
let bx : number
let by : number
// Screen follows 4:3 ratio
let cam_a_x : number = 56
let cam_a_y : number = cam_a_x * 0.75
let cam_c : number = 40
let move_cam : boolean = false
// Timer calc
let s_time : number
let d_time : number
// Music Logic
let playing_song : boolean = false
// Functions for game
function StartSong(name: string)
{
    if (SONGS[name] != null)
    {
        playing_song = true
        timer.background(function(){
            while (playing_song)
            {
                for (let i = 0; i < SONGS[name].length; i++)
                {
                    if (playing_song) SONGS[name][i].play(music.PlaybackMode.UntilDone)
                }
            }
        })
    }
}
function BoolToInt(b: boolean) // Converts Boolean to Integer
{
    if (b)
    {
        return 1;
    }
    return 0;
}
function ShiftCamera(dx: number, dy: number)
{
    move_cam = true
    /**
     * Moves camera using the function f(x)=16ln(x), where x is delta time, until f(x) >= dx or f(y) >= dy
     * If either dx or dy is negative, f(x) will be flipped
     */
    timer.background(function(){
        bx = scene.cameraProperty(CameraProperty.X);
        by = scene.cameraProperty(CameraProperty.Y);
        s_time = game.runtime()
        while (Math.abs(scene.cameraProperty(CameraProperty.X) - bx) < Math.abs(dx) || Math.abs(scene.cameraProperty(CameraProperty.Y) - by) < Math.abs(dy)) {
            d_time = (game.runtime() - s_time) / 1000.0
            if (dx >= 0 && dy >= 0) {
                scene.centerCameraAt(bx + cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx > 0), by + cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy > 0))
            } else if (dx <= 0 && dy >= 0) {
                scene.centerCameraAt(bx - cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx < 0), by + cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy > 0))
            } else if (dx >= 0 && dy <= 0) {
                scene.centerCameraAt(bx + cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx > 0), by - cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy < 0))
            } else {
                scene.centerCameraAt(bx - cam_a_x * Math.log(d_time * cam_c + 1) * BoolToInt(dx < 0), by - cam_a_y * Math.log(d_time * cam_c + 1) * BoolToInt(dy < 0))
            }
            pause(10)
            console.log(scene.cameraProperty(CameraProperty.X) - bx)
        }
        scene.centerCameraAt(bx + dx, by + dy)
        move_cam = false
    })
}
function SetBGImage(img: Image)
{
    BACKGROUND = img
    scene.setBackgroundImage(BACKGROUND)
}
function SetBGColor(color: number)
{
    BACKGROUND = image.create(160, 120)
    BACKGROUND.fill(color)
    scene.setBackgroundImage(BACKGROUND)
}

// Math functions
function GetMagnitude(x: number, y: number)
{
    return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
}

function NormalizeJoystick(speed: number, u: boolean, l: boolean, d: boolean, r: boolean)
{
    f_x = BoolToInt(r) - BoolToInt(l)
    f_y = BoolToInt(d) - BoolToInt(u)
    mag = GetMagnitude(f_x, f_y)
    
    return [speed * (f_x / mag), speed * (f_y / mag)]
}

// Finally I can do some better things


function attachAnim(spr: Sprite, img_anim: Array<Image>, fpms: number, flipped: boolean)
{
    anim = animation.createAnimation(player_a_c, fpms)
    for (let i = 0; i < img_anim.length; i++)
    {
        if (flipped)
        {
            img_anim[i].flipX()
            anim.addAnimationFrame(img_anim[i])
        }
        else
            anim.addAnimationFrame(img_anim[i])
    }
    animation.attachAnimation(spr, anim)
    player_a_c++ // Incremeant for the next player animation
}

// Key Pres Logic Simplification
function IsRightPressed()
{
    return browserEvents.D.isPressed() || browserEvents.ArrowRight.isPressed()
}
function IsLeftPressed()
{
    return browserEvents.A.isPressed() || browserEvents.ArrowLeft.isPressed()
}
function IsUpPressed()
{
    return browserEvents.W.isPressed() || browserEvents.ArrowUp.isPressed()
}
function IsDownPressed()
{
    return browserEvents.S.isPressed() || browserEvents.ArrowDown.isPressed()
}
function IsActionPressed()
{
    for (let i = 0; i < ACTION_KEYS.length; i++)
    {
        if (ACTION_KEYS[i].isPressed()) return true;
    }
    return false;
}
function AttachFunctionToKeys(keys: browserEvents.KeyButton[], cb: () => void)
{
    for (let i = 0; i < keys.length; i++)
    {
        keys[i].onEvent(browserEvents.KeyEvent.Pressed, cb)
    }
}
// -- Title Screen
SetBGImage(assets.image`title`)
pauseUntil(IsActionPressed)

// -- Game initialization
//scene.setBackgroundImage(assets.image`sky`)
SetBGColor(7)
let usr = sprites.create(assets.image`wiz_front`, SpriteKind.Player)
// Makes a hitbox for the player sprite
let usr_hitbox = sprites.create(assets.image`wiz_hitbox`, SpriteKind.Hitbox)
usr_hitbox.setFlag(SpriteFlag.Invisible, true)
// Sword sprite
let sword = sprites.create(assets.image`sword_up`, SpriteKind.Projectile)
sword.setFlag(SpriteFlag.Invisible, true)
// Sprite variables (will be used with other sprites, likely)
sprites.setDataNumber(usr, "action", 0)
sprites.setDataBoolean(usr, "attacking", false)
// Walking Animations
attachAnim(usr, [ // Down Walk Animation (4)
    assets.image`wiz_walk_d_1`,
    assets.image`wiz_walk_d_2`
], WALK_ANIM_TIME, false) 
attachAnim(usr, [ // Right Walk Animation (5)
    assets.image`wiz_walk_r_1`,
    assets.image`wiz_walk_r_2`
], WALK_ANIM_TIME, false)
attachAnim(usr, [ // Left Walk Animation (6)
    assets.image`wiz_walk_r_1`,
    assets.image`wiz_walk_r_2`
], WALK_ANIM_TIME, true)
attachAnim(usr, [ // Up Walk Animation (7)
    assets.image`wiz_walk_u_1`,
    assets.image`wiz_walk_up_2`
], WALK_ANIM_TIME, false)
// attachAnim(usr, [], 250, false)
animation.setAction(usr, 1)
tiles.setCurrentTilemap(assets.tilemap`testing`)
scene.centerCameraAt(80, 60)
StartSong("overworld")
/**
 * ATTACKING LOGIC
 */
AttachFunctionToKeys(ACTION_KEYS, function () { // Lets the game know what
    if (!sprites.readDataBoolean(usr, "attacking")) {
        sprites.setDataBoolean(usr, "attacking", true)
        animation.stopAnimation(animation.AnimationTypes.ImageAnimation, usr)
        //animation.setAction(usr, sprites.readDataNumber(usr, "action"))
        pause(50)
        //animation.setAction(usr, sprites.readDataNumber(usr, "action") + 8) // If it is, play animation
        animation.runImageAnimation(usr, SWING_ANIMATIONS[sprites.readDataNumber(usr, "action")], 250, false)
        switch (sprites.readDataNumber(usr, "action"))
        {
            case 0:
                sword.setImage(assets.image`sword_down`)
                break;
            case 1:
                sword.setImage(assets.image`sword_right`)
                break;
            case 2:
                sword.setImage(assets.image`sword_left`)
                break;
            case 3:
                sword.setImage(assets.image`sword_up`)
                break;
        }
        pause(1000)
        // animation.setAction(usr, sprites.readDataNumber(usr, "action"))
        sprites.setDataBoolean(usr, "attacking", false)
    }
})
// Player Movement & Animation
game.onUpdate(function(){
    v_a = NormalizeJoystick(PLAYER_SPEED * BoolToInt(!sprites.readDataBoolean(usr, "attacking")), IsUpPressed(), IsLeftPressed(), IsDownPressed(), IsRightPressed())
    usr_hitbox.setVelocity(v_a[0], v_a[1])
    usr.setPosition(usr_hitbox.x, usr_hitbox.y)
    // Sets a variable for the user sprite, dependant on what key is being pressed
    if (!sprites.readDataBoolean(usr, "attacking")) // Do not update facing direction once swinging
    {
        if (IsLeftPressed()) {
            sprites.setDataNumber(usr, "action", 2) // Move Left
        } else if (IsRightPressed()) {
            sprites.setDataNumber(usr, "action", 1) // Move/Swing Right
        }
        else if (IsUpPressed()) {
            sprites.setDataNumber(usr, "action", 3) // Move up
        } else if (IsDownPressed()) {
            sprites.setDataNumber(usr, "action", 0) // Move down
        }
    }
    
    // Checks if the player is actually moving
    if (!sprites.readDataBoolean(usr, "attacking"))
    {
        console.log("Player is not attacking")
        if (Math.abs(usr_hitbox.vx) + Math.abs(usr_hitbox.vy) > 0)
            animation.setAction(usr, sprites.readDataNumber(usr, "action")) // If it is, play animation
        else
            animation.stopAnimation(animation.AnimationTypes.ImageAnimation, usr) // Otherwise, stop animation
    }
    // Camera
    if (!move_cam)
    {
        if (usr_hitbox.top > scene.cameraProperty(CameraProperty.Bottom))
            ShiftCamera(0, 114)
        else if (usr_hitbox.bottom < scene.cameraProperty(CameraProperty.Top))
            ShiftCamera(0, -114)
        else if (usr_hitbox.left > scene.cameraProperty(CameraProperty.Right))
            ShiftCamera(144, 0)
        else if (usr_hitbox.right < scene.cameraProperty(CameraProperty.Left))
            ShiftCamera(-144, 0)
    }
})

TL;DR, it was the fact that advanced animation tools held the last frame the sprite was at during the swing. The fix was just using the built in animate with frames block, with looping off

1 Like