Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Game development in Webgl

    Game screen

    This is a guestpost from @Smotko, he does cool things with code and runs my favourite irc channel.

    I’ve started working on a simple game for the Github Game Off competition and I’d like to share my thoughts and findings on creating games with javascript and Webgl. I will not go into Webgl details, as there is a great number of tutorials that do just that, instead I’ll talk about some of the concepts that I used to make my game. You give the game a spin before you start reading, as I’ll be talking about how I implemented a few features and it will be easier for you to follow.

    First of all, why did I choose Webgl? We have canvas, which is full of awesomeness and really easy to use and understand. Webgl, on the other hand, is quite the opposite. There is no drawRect function and in order to draw a simple triangle you need to write ~100 lines of javascript plus a few lines of C like shader code. Drawing textures is not simple either, you need to understand uv texture mapping and concepts such as mipmapping. It’s not a walk in the park. But if you do all the heavy lifting you are rewarded with an application that can have complex 3D models, lightning and it all runs on your graphics card and is therefore insanely fast.

    Structuring your code

    Most gl tutorials never ever tell you how to structure your code. The code is usually packed into a single file, which is OK for a tutorial, but fails miserably as soon as you’ll want to do anything serious. I’ve set up my project into 3 javascript files main.js, game.js and drawables.js. main.js sets everything up. It binds keyevents, creates the shaders, loads the textures and sets up the game object. game.js is where my game object resides. It holds a list of current objects on screen, a list of idle objects off screen and also variables spacifying its state (paused, playing, menu,..), score, gameSpeed (which increases over time) and so on. The drawables.js is where my object hierarchy is defined. I could use a new file for each drawable object, but because each .js file is a new http request (and I can’t be bothered with minifying and/or asynchronous loading) it seemed like a good compromise. At the bottom of the hierarchy I have a simple Square object. A Square has a position, a size, velocity and optionally a texture. Other drawable objects use the Square object to draw their components. As an example the Player object is made of two Square objects. One for the main player model and one for the fork. The fork object is hidden most of the time, but it shows up when the player gets the fork powerup.

    The draw and update functions

    The most important parts of every gl application are the draw and update functions. The update function is where new positions and states of all the objects are calculated. In addition, collisions between objects are checked and user input is processed. All of my drawable objects have an update function. And this update functions is called one time per frame. The update function has one very important parameter - theta. Theta is the time difference between the previous frame and this one. This is necessary as the update functions is not going to be called as often on slow machines as on fast machines and without theta to balance things out, players on fast machines would see objects move faster than players on slower ones. This is why all position updates are being multiplied by theta - to insure all the players have the same experience.

    All of the drawables, unsurprisingly, have a draw function. The draw functions uses the current position of the object to draw the object on screen. As an example, the draw functions of the Square object initializes the vertex, color and texture buffers, sets the shader variables and draws the needed triangles (two of them) so that we get a square. It does all the hard work so all the other drawables don’t have to. Because all of my drawables are stupidly simple - made from just a few squares, their draw functions are trivial. Here is an example of the draw function in my Background object:

    topBar.draw(gl);
    score.draw(gl);
    

    topBar is a Square object, while score is a bunch Square objects - one for each digit.

    Collision detection

    I have made my life really simple in regards to collision detection. All of my objects are essentially squares and I am only checking for collisions between the block objects and the player. This is the function that tells me if two of my objects are colliding:

    areColliding : function(a, b){
    	for (var i=1; i>=0; i--)
    		if(a.position[i] + a.size[i]*a.collisionModifier < b.position[i] - b.size[i] * b.collisionModifier ||
    		   a.position[i] - a.size[i]*a.collisionModifier >  b.position[i] + b.size[i] * b.collisionModifier)
    			return false;
    	return true;
    }
    

    The collisionModifier is interesting because it allows me to make an object bigger or smaller than it actually is.

    Rendering text

    Sprites

    Printing text in OpenGL is not as easy as one would think. It’s usually done by creating a texture atlas with all the characters that you intend to use. When you have your atlas texture loaded, you need to calculate the correct uv coordinates for each character or sprite in the atlas. I’ve created a helper function that calculates the uv coordinates, I only need to specify the sprite offset and the size:

    getTextureUV : function(offset, size){
    	var textureWidth = [0.03125*size[0], 0.03125*size[1]];
    	var u = offset[0]/size[0];
    	var v = offset[1]/size[1];
    	return [
    		// <a class="zem_slink" title="Texture mapping" href="http://en.wikipedia.org/wiki/Texture_mapping" rel="wikipedia" target="_blank">Mapping</a> coordinates for the vertices
    		(u + 1) * textureWidth[0],  v * textureWidth[1],
    		 u      * textureWidth[0],  v * textureWidth[1],
    		(u + 1) * textureWidth[0], (v + 1) * textureWidth[1],
    		 u      * textureWidth[0], (v + 1) * textureWidth[1],
    	];
    }
    

    And now that I can easily access each sprite in the atlas, I can easily draw a character. The size of a character is always one, and I get the position by calling the following function:

    fromChar : function(c){
    	var charIndex = c.charCodeAt(0) - 32;
    	return [(charIndex % NUM_SPRITES), Math.floor(charIndex / NUM_SPRITES)];
    }
    

    Fin

    I’ve learned one more important thing while making this game. Creating games is way more fun than playing them. I got stoked when my game started working and I could move the my in game character across the screen. It’s an incredible feeling that every programmer should experience.

    You can take a look at the actual source code here. If you have any questions feel free to ask me in the comments or on google+ and please let me know if I did something horribly wrong, I want to learn from my mistakes and become batter at this crazy game development thing!

    Published on November 19th, 2012 in Graphics, JavaScript, OpenGL, Programming, Uncategorized, Video game development, Webgl

    Did you enjoy this article?

    Continue reading about Game development in Webgl

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️