How to create a radial button cooldown effect?

I’m looking for hints on how to achieve a radial button cooldown effect, like it’s done in Diablo III for example:

cooldown

The basic technique is a overlay with transparency, but I am stumped on how to achieve the rotating effect.

I would personally just add an animated sprite on top :grinning:

But maybe some geometry experts have another way?

[edit]

Yeah, after looking at a few examples, it’s clearly possible. Look up for how to draw arcs (like this one)

Here’s another example for phaser 2 that is very close to what you want to achieve: here

1 Like

Thanks for pointing me to arc - that seems really close to the solution :slight_smile:
I’ll just have to mask it somehow, so that only the part above the button is visible.

If I make progress, I’ll report back.

Using arc - but with fillpath instead of stroke path and then masking it to the size of the inner part of the button I got this:

cooldown

2 Likes

Because I got a private message about how I solved it, here is my code for the skill butons (typescript) - usage of arc see “update” function.

class SkillButton {
private cdColor = 0x362E3F;
private cooldownEffect: Phaser.GameObjects.Graphics;
private cooldownTime = 0;
private activationTime = 0;
private button: Phaser.GameObjects.Sprite;
private icon: Phaser.GameObjects.Sprite;
private bindOverlay: Phaser.GameObjects.Sprite;
private bindOverlayText: Phaser.GameObjects.BitmapText;

skillId: number;
private readonly rectMask: Phaser.GameObjects.Graphics;

down = false;

constructor(private scene: Phaser.Scene, private action: UIConsumer, x: number, y: number, bindDisplay: string) {
    this.button = scene.add.sprite(x, y, "sprites", "buttonEmpty");
    this.icon = scene.add.sprite(x + 2, y - 2, "sprites", "skill/none");
    let overlay = "ui/keybindBlank";
    if (bindDisplay == "M1") {
        overlay = "ui/keybindMouseLeft";
        bindDisplay = "";
    } else if (bindDisplay == "M2") {
        overlay = "ui/keybindMouseRight";
        bindDisplay = "";
    }
    let offsetX = 18;
    let offsetY = 0;
    this.bindOverlay = scene.add.sprite(x + offsetX, y + offsetY, "sprites", overlay);
    this.bindOverlayText = scene.add.bitmapText(x + offsetX - 2, y + offsetY - 6, "font", bindDisplay, 10);


    this.button.setScrollFactor(0, 0);
    this.button.depth = UI_DEPTH_INGAME;
    this.button.setInteractive();

    this.button.on("pointerover", this.pointerOver, this);
    this.button.on("pointerout", this.pointerOut, this);
    this.button.on("pointerup", this.pointerUp, this);
    this.button.on("pointerdown", this.pointerDown, this);


    this.icon.setScrollFactor(0, 0);
    this.icon.depth = UI_DEPTH_INGAME + 1;
    this.bindOverlay.setScrollFactor(0, 0);
    this.bindOverlay.depth = UI_DEPTH_INGAME + 2;
    this.bindOverlayText.setScrollFactor(0, 0);
    this.bindOverlayText.depth = UI_DEPTH_INGAME + 3;

    this.cooldownEffect = scene.add.graphics();
    this.cooldownEffect.setPosition(x, y);
    this.cooldownEffect.setScrollFactor(0, 0);
    this.cooldownEffect.depth = UI_DEPTH_INGAME + 2;

    this.rectMask = new Phaser.GameObjects.Graphics(scene);

    this.rectMask.fillStyle(0, 1);
    this.rectMask.fillRect(0, 0, 16, 16);
    this.rectMask.setPosition(x - 8, y - 8);
    this.rectMask.setScrollFactor(0, 0);

    this.cooldownEffect.mask = new Phaser.Display.Masks.GeometryMask(scene, this.rectMask);
    this.setOrigin(0, 1);
}

pointerOver() {
    if (this.skillId != undefined) {
        this.button.setFrame("buttonHighlight");
    }
}

pointerOut() {
    this.button.setFrame("buttonEmpty");
}

pointerUp() {
    if (this.down) {
        this.down = false;

        if (this.skillId == DefTypes.SKILL_ID_INVENTROY) {
            this.action.inventoryToggle();
        } else if (this.skillId == DefTypes.SKILL_ID_PORTAL) {
            this.action.castPortal();
        } else {
            //todo available skills zuweisen (button der ihn bisher hatte skill wegnehmen)
            //todo server informieren client-settings (müssen bei welcome zurück kommen)
        }
    }
}

pointerDown(pointer, x, y, event) {
    this.down = true;
}

//todo aufrufen, wenn network das erste mal die available rausrückt (erstmal)
assignSkillId(skillId) {
    if (this.skillId != skillId) {
        this.skillId = skillId;
        if (skillId == undefined) {
            this.icon.setFrame("skill_none");
        } else {
            this.icon.setFrame(ClientDefs.getSkillIcon(skillId));
        }
    }
}

update(time: number) {
    let elapsed = time - this.activationTime;
    let disabled = false;
    if (this.cooldownTime == -1) {
        disabled = true;
    }
    if (!disabled && elapsed > this.cooldownTime) {
        this.cooldownEffect.visible = false;
    } else {
        this.cooldownEffect.visible = true;
        let render = 1;
        if (!disabled) {
            render = Math.floor(360 * elapsed / this.cooldownTime);
        }
        this.cooldownEffect.clear();
        this.cooldownEffect.fillStyle(this.cdColor, 0.4);
        this.cooldownEffect.moveTo(0, 0);
        this.cooldownEffect.arc(0, 0, 16, Phaser.Math.DegToRad(270), Phaser.Math.DegToRad(render - 90), true);
        this.cooldownEffect.fillPath();
    }
}


activate(tick: number, cooldownTimeInMs: number) {
    this.cooldownTime = cooldownTimeInMs;
    this.activationTime = tick;
}

setOrigin(x: number, y: number) {
    this.button.setOrigin(x, y);
    this.icon.setOrigin(x, y);
    this.rectMask.x = this.button.x - (x - 0.5) * 20 - 8;
    this.rectMask.y = this.button.y - (y - 0.5) * 20 - 8;
    this.cooldownEffect.x = this.button.x - (x - 0.5) * 20;
    this.cooldownEffect.y = this.button.y - (y - 0.5) * 20;
}

isReady() {
    return this.skillId != undefined && !this.cooldownEffect.visible;
}

}