This is a continuation of my past blog series on coding an animated character, however I’ll now be going in a different direction with less emphasis on the animation and more on building a modular system to build simple levels with.
Since I want to build a modular system, there needs to be an interface for creating levels. For now, I’m going with the simplest solution, which is to have the user draw pixels onto a canvas, with colors corresponding to different objects. Once the user has a map they’re satisfied with, they should be able to generate a playable version with all the objects in place.
For the HTML, we want one canvas to be our generator, and one canvas to run the game. It’s important to make them have the same ratio, as we will be transposing the smaller one as a map for the larger one. The toolbox for now is just a simple color select with three colors. Two for placing objects, and one for erasing objects.
For the JS, we’ll get the generator canvas that we just made in HTML, but we’ll also create an offscreen canvas to hold the minimum amount of information, while also allowing us to easily keep any pixels drawn locked into a large grid.
As you can see, the dimensions are 16x10 pixels for the offscreen canvas, the same ratio as the other two canvases. This means that our level grid will also be 16x10. Any objects we draw on that grid will need to fit in a 40x40 space, since the game canvas is 640x400.
We’ll create a blank image to use a starting point, just to cover the edge case where a user generates the map without drawing anything. This image will be updated in the draw function any time a change is made to the canvas. To draw on the generator canvas, we’ll add event listeners for when the mouse is pressed down, when the mouse is let go of, and when the mouse moves.
Any time a user tries to draw on the onscreen canvas, it will actually draw on the offscreen canvas, then render the image of the offscreen canvas onto the onscreen one.
There’s not much to the toolbox. Just an event listener that allows the fillStyle to be changed based on which element is clicked on.
Finally, we can get to the game logic. I’m not going to go over every detail, because a lot was already covered in past blogposts, and you can see all the code on the codepen. Anyway, in part 3 of my character animation series I touched on some basic collision logic. That code worked by testing for exclusion. In other words, checking at each frame if the character was not touching the rectangle. This won’t work now that we’re scaling up to have many objects. Even if you’re touching one rectangle, you’re not touching a different one. So the code from the one you’re not touching would constantly interfere with the code that’s trying to stop movement. To get around this, we’ll check for inclusion. To do this we’ll give the rectangles a class just like the skeleton character, and the skeleton will now have two additional move functions, each one designed to counteract the x and y movements. Note that this will only work for rectangular objects. It will not feel right to use this collision code for diagonals or rounded objects.
To check for inclusion inside a rectangle, we’ll need to give both the rectangle’s class and the skeleton class variables for their left, right, down and up borders.
The newly created Box class will be representing rectangles from now on. Let’s write an inclusion based collision code now that we have bounding coordinates for all objects.
As long as the lower coordinate of both is less than the greater coordinate of the other, the rectangles will be overlapping. Here’s an image to help explain:
We also need to know from which direction the objects are meeting, so there are variables to check if obj1 is left, right, up or down from obj2. With these you can check one pixel ahead in the direction the character is moving by taking the sign of it’s x and y vectors. This is a much more robust collision system than we had before, because not only can you have multiple objects on screen, if you manage to glitch further than one pixel inside the wall, it will be able to push you out rather than let you through.
Since we also now have handy center coordinates for our objects, named center for the x direction and z for the y direction, we can use these to sort the order in which we draw objects. This allows objects to dynamically go in front of or behind objects based on the y-axis. Because this allows us to draw the object in a projected psuedo 3D space, I called it “z”, for z-axis.
And here’s the code that draws the objects onto the game canvas:
See all of the code on codepen, and try playing with it for yourself: