Skip to content Skip to sidebar Skip to footer

Rotating A Single Image (but Not The Others) On An Html5 Canvas?

I have a sprite I'm animating on an html canvas using normal sprite sheet blitting. On certain key events I'd like to change the direction of the sprite (ie, flip it, or rotate it

Solution 1:

So I was having this issue with my game; I had cells for up, down, and left animations, but no right cells. So I needed to flip the left cells to draw the right cells.

For each sprite I keep track of it's current top and left in the canvas, as well as each cell's top and left in the sprite sheet.

I've seen previous answers showing a simple horizontal flip as just translating the origin and flipping (inverse scale) of the axes, BUT this does not take into account that with sprites, flipping the origin will mess up the sprite's registration point (its top and left on the canvas).

This issue manifested in the sprite being mirrored correctly, but it's position being off by the width of the sprite. I solved it by taking into account the width of the sprite. Notice how I'm using CanvasRenderingContext2D.prototype.drawImage with 9 arguments since I'm slicing a sprite out of a sprite sheet:

// check if we need to flip image (special case for right movement)
if(sprite.translated){
  context.save();
  context.translate(context.canvas.width, 0);
  context.scale(-1, 1);
  context.drawImage(sprite.sheet,
    cell.left,
    cell.top,
    sprite.width,
    sprite.height,
    // secret sauce: change the destination's X registration point
    context.canvas.width - sprite.left - sprite.width,
    sprite.top,
    sprite.width, sprite.height);
  context.restore();
} else {
  // Assumes cells are all the same width and height, set in sprite
  context.drawImage(sprite.sheet, cell.left, cell.top, sprite.width,
  sprite.height, sprite.left, sprite.top, sprite.width, sprite.height);
}

Note: I also could have done the math in the translate, since it's meant to simplify calculations elsewhere.


Solution 2:

Simply redraw the sprite, with a rotate transformation. Transformations in HTML Canvas 2D Context

The canvas is just an off-screen buffer. It won't be cleared unless you tell it to, and nothing else will be changed unless you tell it to.

There's a bunch of different situations in which you may have to redraw the area of or around the sprite. Otherwise, you'll get a ghosting effect where part of the old sprite is still visible below the new drawing, or other drawings become obscured. Some reasons are:

  • Your sprite is partially transparent,
  • Your sprite is partially translucent,
  • Other drawings are made on top of your sprite,
  • Your sprite is non-rectangular,
  • You're doing flips that are not multiples of 90 degrees.

So that might be a bit more work, and there are several different approaches to doing that. You could simply redraw the entire scene, or just the specific objects at the location, perhaps using the clip method.

A completely different direction might be to use other HTML elements, img or div, with absolute positioning and CSS3 transformations. That's basically a bit of trickery to delegate the rendering of your scene to the browser.


Solution 3:

While I appreciate Shtééf's answer, after a bit of research, I have found that rotating the canvas you are actually using to display doesn't seem to be ideal. The saving, rotating and restoring while trying to create complex animations (aka think Street Fighter 2 not astroids) causes the canvas to flicker in even Chrome.

I have found however a usable strategy. The idea here is that you actually create two canvases, one will be for your game and the other will be a backbuffer of sorts and it will be used to rotate or scale your sprites. You essentially transform the backbuffer canvas, draw the image in question, then transfer it to your main canvas and restore (or not) the backbuffer. In this manner, you only rotate the hidden canvas and only effect the sprite in question not the entire game board.

The code looks something like this (work in progress):

mainContext.clearRect(lastXpos, lastYpos, lastWidth, lastHeight);
backContext.clearRect(0, 0, lastWidth, lastHeight);
lastXpos = xpos;
lastYpos = ypos;
lastWidth = width;
lastHeight = height;


backContext.save();

//check the direction of the sprite
//turn the backContext to this direction   
//SPRITE_INVERTED==-1
if (spriteXDirection == SPRITE_INVERTED || spriteYDirection == SPRITE_INVERTED) 
{
    var horScale = 0;
    var verScale = 0;

    if (spriteXDirection == SPRITE_INVERTED)
    {
    horScale = width;
    }

    if (spriteYDirection == SPRITE_INVERTED)
    {
    verScale = height;
    }


    backContext.translate(horScale, verScale);
    backContext.scale(spriteXDirection, spriteYDirection);


}


//draw the sprite not we always use 0,0 for top/left
backContext.drawImage(animations[currentPlay].sheet,
                animationX,
                animationY,
                width,
                height, 0, 0, width, height);

//Get the image data from the back context
var image = backContext.getImageData(0, 0, width, height);


//flip the back context back to center - or not, I haven't decided how to optimize this yet.
backContext.restore();


//transfer the image to your main context
mainContext.putImageData(image, xpos, ypos);

This has saved me a lot of headaches in understanding how to translate my sprites without having everything on my gameboard move all over the place. It also seems to perform better then modifying the main context.


Solution 4:

Why don't you use save() and restore

ctx.save(); // save current state
ctx.rotate(Math.PI); // rotate
ctx.drawImage(link,x,y,20,20); // draws a chain link or dagger
ctx.restore(); // restore original states (no rotation etc)

How to rotate one image in a canvas?


Post a Comment for "Rotating A Single Image (but Not The Others) On An Html5 Canvas?"