• Monday , 6 April 2020

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

Code Canyon



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

3d Ocean

Related Posts

36 Comments

  1. Dekko
    July 26, 2019 at 14:45

    I think your if, else condition abt the stretched image are wrong…
    correction:
    if(w == undefined && h == undefined) …
    else //stretched

  2. EBEKA Beka
    July 26, 2019 at 14:45

    you are the fuck guys

  3. Steven Kaufman
    July 26, 2019 at 14:45

    Should've wrote if (! filename) instead of writing out all the falsey values (7:05)

  4. antonio young
    July 26, 2019 at 14:45

    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.

  5. No Beats
    July 26, 2019 at 14:45

    Thx so much I've been looking for a channel like this for a week.
    I just don't like using unity because I like to dive deeper in the code and see how everything works.

  6. Dylan Conway
    July 26, 2019 at 14:45

    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?

  7. Abdul Aziz banna
    July 26, 2019 at 14:45

    Awesome tutorial. Really easy to understand.

  8. JavaScript Teacher
    July 26, 2019 at 14:45

    2 years passed and the game engine developed here became Mosaic.js:
    http://www.tigrisgames.com/mosaic

  9. I am love
    July 26, 2019 at 14:45

    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.

  10. I am love
    July 26, 2019 at 14:45

    wow you are a very good teacher!

  11. Scott Jennings
    July 26, 2019 at 14:45

    Great vid!!! fast and easy to understand

  12. Venom !
    July 26, 2019 at 14:45

    nice tut

  13. Martin Nikl
    July 26, 2019 at 14:45

    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.

  14. MinecraftJellybean123
    July 26, 2019 at 14:45

    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);

    });

    });

  15. MinecraftJellybean123
    July 26, 2019 at 14:45

    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);

  16. MinecraftJellybean123
    July 26, 2019 at 14:45

    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!

  17. Denis Chunikhin
    July 26, 2019 at 14:45

    I think, guy which write this tutorial – he don't understand basic of JS.
    I stop to watch after 10:58, coz he append function in constructor.

  18. Tech Dan
    July 26, 2019 at 14:45

    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

  19. Alexander Ebert
    July 26, 2019 at 14:45

    what is the name of this editor ?

  20. Jacob Matthews
    July 26, 2019 at 14:45

    hi try ed your tutorial i couldn't get it to work but that mite just be me i have no coding background

  21. Caleb Prenger
    July 26, 2019 at 14:45

    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"

  22. Levan lomia
    July 26, 2019 at 14:45

    nice tutorial ! everything is clear thank you very much!

  23. ThePodgar
    July 26, 2019 at 14:45

    which font is used at 03:00, it is written wall.png by it ?

  24. Obsidi0n
    July 26, 2019 at 14:45

    How about rotating animated sprites?

  25. Aris Taukulis
    July 26, 2019 at 14:45

    its not working and i dont know why. how do I debug in jfiddle?

  26. seaco2000
    July 26, 2019 at 14:45

    It's a sha,e you can't draw sprites flipped.. 🙁

  27. Sam
    July 26, 2019 at 14:45

    thanks for the clear tutorial. but isn't the context property already deprecated ?

  28. modernator
    July 26, 2019 at 14:45

    brilliant tutorial. thanks!

  29. tuks tuksovski
    July 26, 2019 at 14:45

    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.

  30. Florian Bolli
    July 26, 2019 at 14:45

    Thank you, for this video! 😀
    im looking forward to the next part, too

  31. hermesthephilosopher
    July 26, 2019 at 14:45

    Thanks for the great videos, I'm disappointed there are only two!

    btw I think the line, if(w != undefined || h != undefined) should be == instead of !=

  32. Mike Hamer
    July 26, 2019 at 14:45

    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?

  33. Another Games
    July 26, 2019 at 14:45

    I subscribed and liked without watching the video. First time here.

  34. Deepak Sharma
    July 26, 2019 at 14:45

    Nice tutorial

  35. Mike Ridgway
    July 26, 2019 at 14:45

    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.

  36. David Delikat
    July 26, 2019 at 14:45

    thanks

Leave A Comment

You must be logged in to post a comment.