Game - Space Travels

tutorial project game javascript html

First project for live coding. I will be doing a simple one this time round, a weekend project. The live stream is available on youtube and the game available on https://game-stravel.glitch.me/.

This project, we will be creating a web based game using vanilla web technologies. This game, space travels, features a spaceship travelling along a string of planets with the help of the gravity of the planets.

process

These are the major milestones of getting the game up and running from scratch. The setup process may not be fully documented, but the major game play implementations are all in the live stream.

displaying the environment

First thing is to set up the environment such that it is possible for us to display all our game objects.

I tried the SVG method, but that requires the SVG to be redrawn every round and with the no libraries restrictions, it will be a hassle to implement.

Instead I went on to implement the display mechanism using plain HTML elements. They are set to position:absolute and are positioned using CSS.

the game loop

The game loop is a very simple structure. There is a setup and loop function, and the values are recalculated over and over again when loop is called in intervals.

This structure is very commonly seen in microcontroller programming, and works very well in simple games, especially those where most of the information is stored in a global state.

Most game frameworks have a higher level of abstraction and often stores different elements of the games as objects instead, then have the objects interact on each other. But not in here, we will be handling very vanilla structures.

basic physics

The main thing for all games is the engine. And for this particular game, the main bulk of the game engine is simulate gravity, having the player gravitate towards the planets.

The basic idea is to add the acceleration to the player, depending on the distance from the planet. I made some tweaks to the actual formula for gravity to make the game physics less complex.

function add_gravity(planet){
    if(!planet){ return; }

    var player = game.player;

    var dx = planet.x - player.x;
    var dy = planet.y - player.y;

    // calculate difference in the ratio
    var dr = get_dist(player, planet) / planet.g;
    player.dx += dx/dr;
    player.dy += dy/dr;
}

orbiting around the planet

In actual physics, orbiting around a celestial body is merely an object constantly falling towards the planet. In order to orbit, the player has to have the right velocity at the right distance.

For “non orbital” velocities, the player will be stuck circling the planet in an obtuse and irregular orbit.

For this game, we simplified it a little. Once the player is within range, we set the player to the defined height and move it by a fixed angular velocity.

function orbit(planet){
    var player = game.player;

    // y axis reverse
    var bearing = get_bearing(player, planet) + planet.direction * player.a/fps;
    var dist = player.r + planet.r + planet.a;
    player.x = planet.x + Math.sin(bearing)*dist;
    player.y = planet.y - Math.cos(bearing)*dist;
    player.bearing = bearing;
}

generating new planet

When the player reaches the next planet, the planet will move towards the bottom, dragging the player along.

Once the planet is back at the bottom of the screen, a new planet will be generated for the player to jump to.

function rebase(){
    var y = game.base.y;
    var h = GAME_H - REBASE_P; // REBASE_P being padding

    if(y<h){ y -= REBASE_V; } // REBASE_V being velocity

    // done rebasing
    if(y>=h){
        y = h;
        game.target = gen_planet();
        game.rebase = false;
    }

    var d = y - game.base.y;
    game.base.y -= d;
    game.player.y -= d;

    if(game.base.coins){
        game.base.coins.forEach(function(coin){
            coin.y -= d;
            redraw(coin);
        });
    }

    redraw(game.base);
}

There are a few details about altering the have state to trigger the rebase process, but the main idea is - when the player reaches the target planet, the rebase state is activated and the planet is moved.

refactoring

In the videos, you will see that there is significant lag to the game. I made some changes and refractored the main redrawing process of the HTML DOM elements.

gameplay mechanics

When the key is pressed the player will launch from the planet. In real life physics, the player will be traveling away from the planet at a velocity tangential to the surface.

But not in this game. The physics is simplified to just having the player hop towards the next planet in a straight line.

function jump(planet){
    var player = game.player;
    var bearing = get_bearing(player, planet);

    player.dx = Math.sin(bearing) * player.v;
    player.dy = -Math.cos(bearing) * player.v;
    player.in_orbit = false;
    player.bearing = -1;

    // difficulty
    if(game.jumps%5==0){ player.a += PLAYER_A*0.25; }

    // fast jump bonus
    if(game.base.coins){
        update_score(game.base.coins.length * 3);
    }

    game.jumps += 1;
}

The game engine is considered done. In the live stream, I refactored the code to make it easier to add in new objects. This is in preparation for coding the main gameplay objects like scoring coins and obstacles.

coins

We will be generating the coins for the individual planets. The coins will be generated urrounding the planet, when the player orbits the planet, the found will be collected.

The scoring mechanism for the coins is simple. Each coin eaten (pacman style) will add one point, each coin not eaten will be given two. This is too encourage players to jump to the next planet early.

When the coins are all done up, there is a noticeable lag when the player is orbiting the planet. I suspected that perhaps the coin collision detection is at fault and I made some changes to it.

The whole debugging process is documented in the live stream. The code changes will reflect the change in the way the coin collision is done.

function check_planet_coins(){
    var player = game.player;
    if(player.bearing < 0){ return; }

    game.base.coins.forEach(function(coin){
        if(Math.abs(player.bearing - coin.bearing) <= Math.PI/COIN_MAX){
            game.base.coins.splice(game.base.coins.indexOf(coin),1);
            game.canvas.removeChild(coin);
            update_score();
        }
    });
}

coin paths

Other than the planet’s coins, the other way of obtaining coins will by taking the optimal path from one planet to the other.

A line of coins will be generated on the path between the two planets. A distance check will be used to check for coin collisions.

function gen_planet(){
    // truncated //

    // planet coins
    var num = Math.round(Math.random() * (COIN_MAX-COIN_MIN)) + COIN_MIN;
    var da = 2*Math.PI / num;
    var dist = PLANET_R + COIN_A + COIN_R;

    planet.coins = [];
    for(var i=0; i<num; i++){
        var coin = init_circle(COIN_R);
        coin.className = 'coin';

        coin.x = planet.x + dist*Math.sin(da*i);
        coin.y = planet.y - dist*Math.cos(da*i);
        coin.bearing = da*i;

        redraw(coin);
        planet.coins.push(coin);
        game.canvas.appendChild(coin);
    }

    // truncated //
}

generating the sun

The first obstacle we have is the sun. The sun is moved into position then it’s gravity will be added to the player’s velocity.

function gen_planet(){
    // truncated //

    // sun
    var have_sun = false;
    if(game.jumps>SUN_J && Math.random()<SUN_T){ have_sun = true; }

    if(have_sun){
        var sun = init_circle(SUN_R);
        sun.className = 'sun';

        sun.x = -0.5*SUN_R + (planet.x<(GAME_W/2))*(GAME_W+SUN_R);
        sun.y = GAME_H / 4;
        sun.g = SUN_G;

        redraw(sun);
        game.sun = sun;
        game.canvas.appendChild(sun);
    }

    // truncated //
}

obstacle 2 - asteroids

Asteroid are located in the middle of the two planets. They obstruct the player’s clear path to the target planet.

The asteroid is shifted slightly from the direct path to give the player more leeway to fly to the next planet.

function gen_planet(){
    // truncated //

    // stone
    var have_stone = false;
    if(!have_sun && game.jumps>STONE_J && Math.random()<STONE_T){
        have_stone = true;
    }

    if(have_stone){
        var stone = init_circle(STONE_R);
        stone.className = 'stone';

        var bearing = get_bearing(game.base, planet);
        var dist = PLANET_R + STONE_R + STONE_D;

        stone.x = planet.x + Math.sin(bearing) * dist - STONE_R;
        stone.x += 2*(Math.random()<0.5)*STONE_R;
        stone.y = planet.y - Math.cos(bearing) * dist;

        redraw(stone);
        game.stone = stone;
        game.canvas.appendChild(stone);
    }

    // truncated //
}

online score board

The database is a very simple Google app script that is connected to a spreadsheet. It is really just a record of the current scores but it works well enough for our game.

Perhaps if I am up to it, there will be another tutorial for this. But in here, we will only be highlighting the integration process of sending and receiving the high scores.

function submit_score(score){
    var name = prompt('name');
    if(!name){ return; }

    var xhr = new XMLHttpRequest();
    xhr.open('POST', score_url+'&name='+name+'&score='+score);
    xhr.onreadystatechange = function(){
        if(xhr.readyState==4 && xhr.status==200){
            if(!parseInt(xhr.responseText)){
                alert('unable to submit score.');
            }else{
                get_highscore();
            }
        }
    };
    xhr.send();
}

function get_highscore(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', score_url);
    xhr.onreadystatechange = function(){
        if(xhr.readyState==4 && xhr.status==200){
            var scores = JSON.parse(xhr.responseText)['top'];

            $('highscore').innerHTML = '';
            var div = document.createElement('ul');
            scores.forEach(function(score){
                var ele = document.createElement('li');
                ele.innerHTML = '<strong>'+score.name+'</strong><br />'+score.score;
                div.appendChild(ele);
            });

            $('highscore').appendChild(div);
        }
    };
    xhr.send();
}

fin

And that’s basically the whole process of creating a simple web based game in vanilla javascript. Hopefully, I will be having more game jams sometime soon.