JavaScript HTML Game Development Tutorial 2 – Drawing Stretched and Rotated Sprites on Canvas




Source code: http://www.tigrisgames.com/src

This is my next HTML canvas game engine tutorial in the series. So let’s continue our journey on our way to learning how to develop a 2D tile mapped HTML game engine (In the style of Mario platformer, but with a few unique twists of our own.) This Java Script game development tutorial was created to help learn how to draw image sprites / pictures on JavaScript canvas, in preparation to making your first JavaScript game engine.

First we will make sure our canvas is set up, this is a must for game dev. as we deviate from HTML div element-based sprites you may already be familiar with. (We want to use HTML5 canvas tag this time, not DIV elements as sprites!)

JavaScript objects are important. And so, after our JavaScript canvas has been set up, we will create a new JavaScript object conveniently called “Sprite” and add methods draw and rotate. Original size, stretched and rotated 2D tiles (32 by 32 pixels) are covered.

It’s a good gamedev tutorial if you’re starting out to make an 2D platformer or an adventure RPG game but still need to brush up on JavaScript Canvas code.

As a bonus I decided to include an animated sprite, which is just a tile with some sprite rotation applied to it, using JavaScript’s setInterval time function.

Source code from this sprite drawing tutorial is available here: http://jsfiddle.net/gregsidelnikov/dzuwLr8t/22/

Help me make my next JavaScript gamedev tutorial 🙂
https://www.patreon.com/tigrisgames

Original source


36 responses to “JavaScript HTML Game Development Tutorial 2 – Drawing Stretched and Rotated Sprites on Canvas”

  1. for anybody that's having problems with the stretch image displaying, you need to go to your draw() function and find this line:

          // Image?
          if (w != undefined || h != undefined) {
            Context.context.drawImage(this.image, x, y, this.image.width, this.image.height);
          }

    And you need to change it to this:

          // Image?
          if (w === undefined || h === undefined) {
            Context.context.drawImage(this.image, x, y, this.image.width, this.image.height);
          }

    So now your code should read: (CORRECT)

    if (w 'is not specified' or h 'is not specified') {
    then Create canvas with a 2d context and drawImage(with the image,
    at position a x, at a position y, using the image width, using the image height);
    }

    And not: (WRONG)

    if (w 'is specified'  or h 'is specified') {
       then Create canvas with a 2d context and drawImage(with the image,
    at position a x,  at a position y, using the image width, using the image height);
    }

    Because if you specified the 'w' and 'h' you want to use those values and not the values of the image file itself. Now our 'Else' statement for displaying the stretched image will work.

  2. so i can get the tutorial to work with the image URLs you provide in the tutorial but if i try to use an image from my computer i created that is the same size (32 by 32) the code doesnt work. also if i download one of the images from the tutorial and use it from my computer, the code still does not work even though it is the exact same image. do you know of any reason why this is happening?

  3. Am I required to have some what of a javascript background for this? If so how long will it take to get a javascript background? which tutorial do you recommend? There are a few things I do not understand like functions with return values and this.pattern and I'm just having a hard time understanding the foundation and connecting all the dots. Not to say I couldn't copy and paste some code and make a video game but that is not the goal here. The goal is to build something from the ground up and understand it so I can adapt for future projects.

  4. Very nice introduction to canvas. I can see a problem with image loading however. Images are loaded asynchronly. It cause troubles with their drawing to canvas. If image is not ready yet and I call Sprite.draw() method, then image cannot be drawn on canvas. You have to check first if all images are ready! You can use this.image.onload() function ideally.

  5. Sigh I rewatched the video and rewrote code but its not working again… Find my mistake and then paste me a copy of the right code so I can memorize it and copy it into mine.
    var Context = {
    canvas : null,
    context : null,
    create: function(canvas_tag_id) {
    this.canvas = document.getElementById(canvas_tag_id);
    this.context = this.canvas.getContext('2d');
    return this.context;
    }
    };

    var Sprite = function(filename, is_pattern) {

    this.image = null;
    this.pattern = null;
    this.TO_RADIANS = Math.PI/180;

    if (filename != undefine && filename != "" && filename != null)
    {
    this.image = new Image();
    this.imagesrc = filename;

    if (is_pattern)
    this.pattern = Context.context.createPattern(this.image, "repeat");

    }
    else
    console.log("Unable to load sprite.");

    this.draw = function(x, y, w; j)
    {
    // Pattern?
    if (this.pattern != null)
    {
    Context.context.fillStyle = this.pattern
    Context.context.fillRect(x, y, w, h);

    } else {

    // Image
    if (w != undefined | | h != undefined)
    {
    Context.context.drawImage(this.image, x, y,
    -(this.image.width,
    -(this.image.height)));

    Context.context.restore();

    } else {

    // Stretched
    Context.context.drawImage(this.iamge, x, y, w, h);

    }
    }
    };

    this.rotate = function(x, y, angle)
    {
    Context.context.save();
    Context.context.translate(x, y);
    Context.context.rotate(angle * this.TO_RADIANS);
    Context.context.drawImage(This.image,
    -(this.image.width/2)
    -(this.image.height/2)
    };

    };

    $(document).ready(function() {

    // Initialize Canvas
    Context.create("canvas")

    var WALL = "Sick Cool Dirt-20161125-080303.piskel"
    var CRATE = "Sick Cool Dirt-20161125-080303.piskel"

    var image = new Sprite(WALL, false);
    var image2 = new Sprite(CRATE, false);
    var pattern = new Sprite(CRATE, true);
    var angle = 0;

    setInterval(functon() {

    Context.context.fillStyle = "#000000";
    Context.context.fillRect(0, 0, 800, 800);

    image.draw(0, 0, 64, 64);
    image.draw(0, 74, 256, 32);
    pattern.draw(160, 160, 256, 180);

    image.rotate(115, 160, angle += 4.0);
    image2.rotate(115, 260, -angle/2);

    });

    });

  6. Whats wrong with my code? Im not showing html because I know im right with that one :

    var Context = {
    canvas : null,
    context : null,
    create: function(canvas_tag_id) {
    this.canvas = document.getElementById(canvas_tag_id);
    this.context = this.canvas.getContext('2d');
    return this.context;
    }
    };

    var Sprite = function(filename, is_pattern) {
    // Construct
    this.image = null;
    this.pattern = null;
    this.TO_RADIANS = Math.PI/180;

    if (filename != undefined && filename != "" && filename != null) {
    this.image = new Image();
    this.image.src = filename;

    if (is_pattern)
    {
    this.pattern = Context.context.createPattern(this.image, 'repeat');
    }

    }
    else
    console.log("Unable To Load The Sprite/Graphic")
    prompt("There has been an error! (Check the console)")

    this.draw – function(x, y, w; h)
    {}
    // Pattern?
    if (this.pattern != null)
    {
    Context.context.fillStyle = this.pattern;
    Context.context.fillRect(x, y, w, h);
    } else {

    // Image
    if (w != undefined | | h != undefined)
    {
    Context.context.drawImage(this.image, x, y,
    this.image.width,
    this.image.height);

    } else {

    // Stretched
    Context.context.drawImage(this.image, x, y, w, h);

    }

    }
    };

    this.rotate = function(x, y, angle)
    {
    Context.context.save();
    Context.context.translate(x, y);
    Context.context.rotate(angle * this.TO_RADIANS);
    Context.context.drawImage(this.image,
    -(this.image.width/2),
    -(this.image.height/2));

    Context.context.restore();
    };

    };

    $(document).ready(function() {

    // Initialize
    Context.create("canvas");

    Context.context.beginPath();
    Context.context.rect(0, 0, 640, 480);
    Context.context.fillStyle = "black";
    Context.context.fill();

    $(document).ready(function()) {

    // Initialize Canvas
    Context.create("canvas");

    var WALL = "http://www.tigrisgames.com/wall.png";
    var CRATE = "http://www.tigrisgames.com/crate.png";

    var image = new Sprite(WALL, false);
    var image2 = new Sprite(CRATE, false);
    var pattern = new Sprite(CRATE, true);
    var angle = 0;

    setInterval(function() {

    Context.context.fillStyle = "#000000";
    Context.context.fillRect(0,0,800,800);

    image.draw(0,0,64,64);
    image.draw(0,74,256,32);
    pattern.draw(160,160,256,180);

    image.rotate(115,160, angle += 4.0);
    image2.rotate(115,260, -angle/2);
    }, 25);

  7. Woah! These tutorials are great! I watched other tutorials where the guy types really fast and doesn't go over the command. Hes just like, do this do that, copy and paste. Im nine and I am still learning code so I really need these tutorials… Thank you for this!

  8. Hey thanks for the tut! I'm having a bit of an issue. In the Sprite constructor the this.pattern property isn't being populated by Context.context.createPattern(this.image, 'repeat'); .. I think it might be an Image object load issue but I moved it to the draw function Context.context.fillStyle = Context.context.createPattern(this.image, 'repeat'); .. I console logged the error and the first loop comes out Null and the other ones populate so I'm sure it's gotta be a loading issue. The way i did it isn't probably the best since it's going to loop over that instead of being pre-defined. You think you got a solution for this?

    var Sprite = function(filename, is_pattern) {
    this.image = null;
    this.pattern = null;
    this.TO_RADIANS = Math.PI/180;
    if (filename != undefined && filename != '' && filename != null) {
    this.image = new Image();
    this.image.src = filename;
    if (is_pattern) {
    this.pattern = true; // <—– Switch this to true
    }

    } else {
    console.log('Unable to load sprite');
    }

    this.draw = function(x, y, w, h) {
    // Pattern
    if (this.pattern != null) {
    Context.context.fillStyle = Context.context.createPattern(this.image, 'repeat'); <—— Moved the create Pattern method here to loop if null
    Context.context.fillRect(x, y, w, h);
    } else {
    // Image
    if (w == undefined || h == undefined) {
    Context.context.drawImage(this.image, x, y, this.image.width, this.image.height);
    } else {
    // Stretched Image
    Context.context.drawImage(this.image, x, y, w, h);
    }
    }
    };
    ………….. etc

  9. I think you have a few errors in code. you say if "w != undefined || h != undefined". Then you use the image width and height instead of the w and h. Then in the else statement you use them. So you said in the else "if w and h are undefined,use them"

  10. I just have to point out the mistake you are making at 07:02. Since you are not using !== but != , then filename!=undefined and filename!=null are the exact same thing. I think you should know that by now. This whole expression could just be replaced with if(filename), it will evaluate to true if filename is not null, undefined, empty string, 0, NaN or false. Otherwise, thanks for the tutorial.

  11. How do you get away with calling the sprites draw method outside of the images onload function? In order to get this code to work I have to call the draw method like this.
    image.image.onload = function() {
        image.draw(x, y);
    }

    why does it work for you?

  12. Quick and dirty first pass of the first third of the video: Done using the DIYCaptions editor.
    beta hosted here: http://mikeridgway.com/diycaptions/newstart1.php?id=I3Ik81Ku3lA
    See http://www.DIYCaptions.com for more information. Twitter:@DIYCaptions
    Update: Did a second pass where I corrected several errors. The text below has been updated accordingly.
    = = = = 
    0:00  Hey guys. In this tutorial, I'm gonna show you how to draw
    0:03  pictures on the screen using JavaScript canvas. What you're seeing right now
    0:08  is a picture of a video game. And in this game, the entire level
    0:13  is made out of smaller sprites – 32 by 32 pixels.
    0:18  So I'm gonna show you how to draw just one sprite.
    0:21  And maybe in the future tutorials, we're gonna learn how to draw the entire
    0:26  world
    0:27  using these sprites. By the end of this tutorial, we're going
    0:33  to have have something like this. This is a regular sprite.
    0:37  This is a stretched sprite. This is tiled sprite
    0:41  sprite with a pattern. And these are rotating sprites.
    0:49  First, we need to initialize the context.
    0:51  And this is the code from my previous tutorial. Here
    0:56  I created a JavaScript object called context. And
    0:59  really, to initialize our canvas,
    1:03  all we need to do is these two steps. You don't really need to do this
    1:07  inside of an object. You don't have to create this object.
    1:11  These two steps you can do separately inside the main
    1:15  game function. But the reason I did it this way is because
    1:19  it's easier, in the future, to refer to it
    1:22  something like this: Context.create("canvas"). "canvas"
    1:26  is the ID tag of the canvas tag.
    1:32  And creating
    1:34  a function around these two functions
    1:38  that initialize context makes it much more convenient to
    1:43  as a user interface to refer to
    1:48  create the canvas. And then we can just move on
    1:51  and start rendering sprites on the canvas once it's created.
    1:59  This is the sprite that I chose – that I'm going to use for this example.
    2:03  And I called it wall.png.
    2:06  I use .png files on canvas
    2:09  because it automatically has transparency effects.
    2:13  So there's no need to write any additional code if you
    2:16  want to render transparent sprites
    2:19  For example, if there's another sprite, and it has transparency,
    2:23  and you move it over whatever you just drew
    2:27  on the canvas, you will have automatic transparency.
    2:30  All you have to do is edit the picture in Photoshop
    2:33  and add transparent areas. So this will
    2:37  automatically do that for you. This sprite is
    2:41  scaled up. It's zoomed in. By the way, this is not the canvas right now.
    2:46  I'm just doing this in Photoshop
    2:47  just as an example.
    2:51  But this is a scaled tile in Photoshop.
    2:55  When I render it on the canvas, it's gonna be smaller.
    2:58  Each pixel will be actually a pixel – not several 
    3:01  ones like here. I just wanted to zoom this in to show you what the sprite is.
    3:06  This is actually 32 by 32 pixels. So 
    3:10  in order to start drawing sprites, I'm going to create a JavaScript object
    3:14  called sprite. And just like we did
    3:18  did with canvas, where you can create a canvas object and then create it,
    3:23  in the same way, we're gonna create a sprite object,
    3:26  then we're gonna add some functions to it, so we can actually load
    3:30  the image into it, and then draw
    3:33  it on the actual canvas
    3:40  So now I'm ready to start writing the sprite function
    3:43  And it's an object of type function,
    3:47  which makes it a constructor. And it's going to take a file name
    3:51  of the actual picture on the hard drive. And
    3:55  another thing we'll need is we'll need to identify whether this is just a
    4:00  regular sprite or a pattern,
    4:04  like a background pattern. And you'll see how
    4:07  that's used – how this flag is used later
    4:12  on in the code. So the first thing that this object needs
    4:16  is somewhere –
    4:19  a place to hold the image object. And
    4:23  I'll call it this.image.
    4:26  The key word this attaches this 
    4:31  property to to this object. And
    4:34  another thing that we might need:
    4:38  the way the canvas  processes regular sprites is a little different
    4:41  from the way it processes patterns. So for this reason, we need to
    4:45  add a special object –
    4:49  pattern. And the canvas will choose between the two
    4:53  within this function, as you will see later. And one more thing
    4:58  we need is, we need to define this
    5:03  number – which is a floating-point
    5:06  number – that will allow us to
    5:11  do some rotation calculations properly
    5:14  in degrees – because by default, the degrees function –
    5:18  it's not in degrees it's in radians.
    5:21  So we need to
    5:25  convert the angle to radians, from degrees. You will see how that's used
    5:32  later in this object. So, because this
    5:36  sprite object is a constructor object,
    5:39  basically, everything that you see here is going to run
    5:43  every time you start a new object.
    5:47  For example, by calling
    5:51  the sprite
    5:55  object with a "new" keyword.
    5:59  So this is all going to – well it's going to create
    6:04  new picture using this object. So every time you do this,
    6:07  all of this code will run. That's why this is a constructor
    6:12  object. That's what it means. It constructs this object every time you
    6:16  call this object with a "new" operator. But let's forget about that for a second
    6:21  and finish this object that will
    6:25  run the code.
    6:28  And first, we need to construct
    6:31  the rest of the object. And we can probably say that
    6:37  this construction begins here inside the object. What we need to do next,
    6:41  we need to check if the file name is actually –
    6:44  if it was actually passed
    6:47  undefined. If it's not undefined,
    6:50  undefined – if the file name is not
    6:55  empty, and file name
    6:59  if not null. So,
    7:03  if we have a valid file name,
    7:06  we can start loading the image.
    7:10  And the way it's done, we're going to take this
    7:13  object here that we created, and we're going to start a new
    7:18  object image. Image is
    7:21  a built in JavaScript object
    7:25  for dealing with the images. And
    7:28  in order to load it, all we need to do is assign
    7:32  image.src
    7:36  assign the file name
    7:39  to it. And that will actually load
    7:42  the picture. So that's it. There is nothing else to do. One more thing we need to test for is the
    7:48  if is_pattern flag.
    7:52  So let's say somebody passed is_pattern as
    7:56  true, and we need to do something here about it, because
    8:01  the canvas doesn't render images and patterns in the same way.
    8:06  And so we'll take
    8:10  this object – this second object in the constructor,
    8:14  and we'll use the context – the context object –
    8:19  from the previous tutorial, and earlier in this one,
    8:22  when we created Context here with this function –
    8:26  we're using the same Context here. And it has 
    8:31  a createPattern method.
    8:34  So we need to pass this image that was just loaded
    8:38  into it, and specify repeat.
    8:43  So this will create the pattern. And, of course,
    8:49  if the file name wasn't passed, we should probably
    8:52  log it into the console by saying
    8:55  unable to load sprite. So if something's wrong with the image,
    9:00  or if it's not present on your hard drive, or
    9:04  if something's wrong with the file name, you'll see on the console –
    9:07  it will let you know. So,
    9:10  at this point, we have successfully loaded the sprite.

Leave a Reply