Tutorial – Making a game in SVG

Introduction

One of chrome’s best easter egg is the offline side scroller dino game. In this tutorial, I will be teaching you how to recreate a simplified version of it in SVG.

For this project, we will be using handlebars library to draw updates to the screen as it scrolls along.

note: the javascript in this tutorial is incomplete (i.e. each section only contains the relevant parts). To view the full code, visit the codepen.

Setting up the canvas

For a start, we will setup the code to render the SVG. We will only display the ground and the dino for now.

Every game will have a update loop to make the necessary movements and redraw the display. Our update loop will contain the code to render the game’s SVG figure.

This update loop is usually accompanied with a fps variable, indicating how long apart each update function call is.

// gid is shortcut for document.getElementById
var template = Handlebars.compile(gid('template').innerHTML);
var base = 280;
var fps = 20;

var data = {
  dino_y:base 
};

function update(){
  gid('game').innerHTML = template(data);
}

The handlebars template for generating the game contains only the background, floor and the dino, as of now.

<svg viewBox="0 0 800 400">
  <style><![CDATA[
#bg{
  fill:#66F;
}

#dino{
  fill:#0FA;
}

.obj{
  fill:#0F0;
}

text{
  fill:#FFF;
  font-family: Helvetica, Arial, Sans-serif;
  font-size: 40px;
  font-weight:800;
}

#game{
  font-size:80px;
}
  ]]></style>

  <rect id="bg" x="0" y="0" width="800" height="400"></rect>
  <rect id="ground" x="0" y="380" width="800" height="20" fill="#FF0"></rect>
  <rect id="dino" x="80" y="{{dino_y}}" width="80" height="100"></rect>
</svg>

Making the dino jump

Implementation To receive the clicks, we will have an overlay that will spread across the screen.

The jump function is really simple, there will be 3 main variables for this function, dino_y, dino_dy, and env_g.

The first variable, dino_y stores the current y coordinates of the player. dino_dy represents the vertical speed, positive for moving upwards, negative for moving downwards. env_g is gravity. Using these 3 variables, and some simple physics, we can make the dino jump.

Here, we will be making good use of the fps variable. Each variable change will be in terms of 1 seconds, so the fps will be used to add increments of the change during each update.

var data = {
  // ...
  dino_flr:true,
  dino_jump:800,

  /* main 3 variables for jumping calculation */
  env_g:1200,
  dino_y:base,
  dino_dy:0
}

function jump(){
  /* only one jump at a time */
  if(data.dino_flr){
    data.dino_dy = data.dino_jump;
    data.dino_flr = false;
  }
}


function update(){  
  /* change in speed and position */
  data.dino_dy -= data.env_g/fps;
  data.dino_y -= data.dino_dy/fps;

  if(data.dino_y>base){
    data.dino_y = base;
    data.dino_flr = true;
  }

  // ...
}

For each update loop, an increment of x/fps is added. After 1 second, the full value x will be eventually added.

Creating obstacles

In the game, there are all these different different obstacles that will be created on the fly. This part will show you a method of generating the obstacles.

There will be 3 classes of obstacles, small, medium and large, correponding to the different obstaclesin the original game, spike, small and large cactus.

For each set of obstacles (cactus or spikes), we will have a random distance between them (between 500px to 1200px).

Then depending on the type of obstacles, there will be different number of it in each set. There will always be 3 spkies, 2 to 3 small cactus, and 1 to 2 large cactus in our game.

Of course, we will have to animate the obstacles too. For that we will have the env_dx variable to set the speed of the obstacles, which will be used in the update loop to move the obstacles along.

var data = {
  // ...
  objs: []
}

function createObjs(){
  /* staring generating from x=80 */
  var last = 80;
  var objs = data.objs;

  /* get x position of the last obstacle */
  if(objs.length){
    last = objs[objs.length-1]['x'];
  }

  /* dunnid to generate until too far */
  if(last>1200){ return; }

  while(last<1200){
    last += rand(5,12)*100;

    /* option of spike, small or large cactus */
    var opt = rand(2);
    var h = [50,100,150][opt];
    var w = [50,50,60][opt];

    /* random count of the obstacles in each set */
    var c = rand([3,1,1][opt],[4,3,2][opt]);
    for(var i=0; i<c; i++){
      objs.push({
        x:last+(10+w)*i,
        y:380-h,
        w:w,
        h:h
      });
    }
  }
}

function update(){
  /* moving existing obstacles */
  for(x in data.objs){
    data.objs[x]['x'] -= data.env_dx/fps;
    if(data.objs[x]['x'] < -500){ delete data.objs[x]; }
  }

  createObjs();
  //...
}

In the update loop, the createObjs function is called to replenish the obstacles after the past spikes and cacti have long moved off the screen, thus creating an “infinite level”.

Setting the score

For the original dino game, the score is depedent on how long you survive in the game. We will thus have another timeout just to increment the score, 10 points per second of surviving.

function score(){
  data.env_s += 1;
  window.to_score = setTimeout(score,100);
}

Colliding with the dino

Since all the objects in the game are rectangles, we can use the simple boundary box collision test.

figure for collision test

The brief idea is that the x-axis segments of the 2 bounding boxes cannot collide, same goes to the corresponding y-axis segments.

function collide(obj){
  /* player's bounding box boundary */
  var dino = {
    x1:80,
    x2:160,
    y1:data.dino_y,
    y2:data.dino_y + 100
  };

  /* object's bounding box boundary */
  var wall = {
    x1:obj.x,
    x2:obj.x + obj.w,
    y2:380,
    y1:380 - obj.h
  };

  /* simple rect obj collision */
  if(wall.x1<dino.x2 && wall.x2>dino.x1 && wall.y1<dino.y2 && wall.y2>dino.y1){
    return true;
  }

  return false;
}

function update(){
  for(x in data.objs){
    if(!x){ continue;}

    /* only check if the obstacle is close enough */
    if(data.objs[x].x<200){
      if(collide(data.objs[x])){
        // we will handle this in more detail later
        alert('lost');
        return;
      };
    }
  }
}

The collide function takes in the information of an obstacle, gets its extreme boundaries and compares it with the player’s extreme boundaries. If there is a collision, the function will return true.

Reseting the game

Time for some refactoring. To create a game that can be started and restarted over and over again, we will wrap all the initialization and the start of the update loop into a reset function.

Along with it, we should have a function to end the update loop, this will be aptly named lost. The will contain all the code for removing the timeouts and resetting the score.

function reset(){
  window.data = {
    env_g:1200,
    env_s:0,
    env_dx:300,
    dino_y:base,
    dino_dy:0,
    dino_jump:800,
    dino_flr:true,
    objs:[] 
  };

  update();
  score();
}

function lost(){
  alert('lost');
  /* can add other stuff if you want */
}

function update(){
  /* collision */
  for(x in data.objs){
    if(!x){ continue; }

    if(data.objs[x].x<200){
      if(collide(data.objs[x])){
        lost();
        return;
      };
    }
  }

  //...
}

Making it harder

There are a many improvements you can make to make the game more challenging. Here are some ideas:

  • Increase speed at higher scores
  • Increase gravity so player drop faster
  • Reduce distance between obstacles to reduce time for reaction
  • Increase the number of obstacles per set, making the obstacle set longer

As for the code, I will leave it out as an implementation exercise. Have fun!

codepen.io

Advertisements

Getting Started 1: Markdown – Simple formatting for your documents

This will be the first post of a series where I teach frontend developement. This series will include everything you need to know, sufficient to get you started with the basic tools that can help you create your site.

All web pages have formatted structure and content. Markdown exposes the most commonly used structures in web documents, so you can produce web documents without any knowledge of HTML.

Markdown is a widely adopted tool, and can be used to write your posts on WordPress and Tumblr, among other places. It is great place to start gaining more control over your content.

Trying it out

You can try Markdown using a web markdown editor, such as dillinger.io. But for those with more time on their hands, you can download the official markdown compiler to generate your web document (instructions here).

Basic markdown syntax

Here are a few basic formatting that you will be using very often in your documents.

# H1 Header: Title of the document
## H2 Header: Subtitle of the document
### H3 Header: Sub-subtitle of the document
#### H4 Header: It goes all the way to H6, so…
Do remember the space after the hashes

– Unordered list item1
* Unordered list item2
+ Unordered list item3
Yep, you can use either "-", "*" or "+"

1. Ordered list item1
2. Ordered list item2
5. Ordered list item3
It doesn't matter the order of items, they will all appear in the same list.

**Bold Text**
__This is bold text too__
These will be surround by <strong> tags (more on that in the next tutorials).

*Italics Text*
_This works too_
Surrounded in <em> tags, it means emphasised text (we'll cover that later too).

> quoteblock
> > Yep, quoteblocks can be nested
Quoteblocks are separated blocks of text that can be styled different from the rest of the text.

`inline code`
“ If you want to put "`" in your code, this is the way to go. Do note the space after the double backticks. “


***
Horizontal rule (one line across the document), both works

Links and Images

Links and images can be written differently, either inline or referenced.

Inline format for links
[Link Text](Link URL)
[Google](http://www.google.com)

Inline format for images
![Alt text for image](Link to image)
![Example Image](http://path/to/image.jpg)

Reference style format
[Google][1]
[1]: http://www.google.com (optional title)

Self referring format
[Google][]
[google]: http://www.google.com (name not case sensitive)

The references can be placed anywhere in the document, after the paragraph they are used, or at the end of the document, like footnotes.

Where to go after this

Markdown is just a basic formatting for your documents. All these are compiled into HTML(hyper-text markup language) which when viewed in browsers, comes will all the appropriate styling.

In the next tutorial, we will delve deeper into HTML itself, which gives you a better understandin of the HTML file that Markdown outputs.

I want more

There are many different markup languages, geared towards different usages. Personally, I would recommend you to learn and master GitHub Flavoured Markdown. It is one of the few widely adopted extension for markdown and can be used in many blogging and content creation platforms (dillinger supports GFM too).

The extended Github Flavoured Markdown includes many syntax to format more complex document structures like tables, code blocks, and many more.

Fountain is a markup language specially designed for writing scripts for plays. There are many different formatting for standard elements in plays, such as the setting, dialog, etc.

There are more of course, so, go forth and explore them on your own.

Tutorial – Using sessions in web.py

Intro

Sessions in web.py are like server-side cookies. Cookies are objects used to store simple information for either identification purposes or to keep track of user’s preference. (If you already knew that, give yourself a cookie -> yep, that brown backed confection). Sessions are used to identify different users on the website, they make use of client-side cookies and IP address for identification, among other things.

When you try out other tutorials out there, you might find yourself stuck at some parts, getting random errors that makes little sense to you. I have been through that process and I am here to share how it is actually done.

For the tutorial, I will be making a simple counter app that makes use of sessions to track the number of times a page is visited. There is an explained version for those that are not too used to webpy framework and could use some help with the code.

app.py (short and sweet version):

import web

urls = [
    '/', 'Index',
    'k', 'Kill'
]
app = web.application(urls, globals())

if not web.config.get('session'):
    session = web.session.Session(app, 
        web.session.DiskStore('./sessions'),
        initializer={'count':0}
    )
    web.config.session = session
else:
    session = web.config.session

class Index:
    def GET(self):
        session['count'] += 1
        return session['count']

class Kill:
    def GET(self):
        return web.seeother('/')

if __name__ == '__main__':
    app.run()

app.py (explained):

Readings