Click here to Skip to main content
15,867,308 members
Articles / Web Development / HTML5

SpaceShoot Single-Player

Rate me:
Please Sign up or sign in to vote.
4.98/5 (46 votes)
14 Feb 2012CPOL18 min read 72.3K   1.7K   65   52
Building a full featured (and fun-packed) single-player game out of the box.

The SpaceShoot Singleplayer in action

Introduction

In the original SpaceShoot article, the blueprint for a full featured HTML5 game was presented. This time we implement many of the ideas that were presented previously for extending the game. This article will present the game itself and part of its code. We will walk through the following topics:

  1. Building a menu in HTML and controlling it over JavaScript
  2. Using LocalStorage to save settings and personal high-scores
  3. Coding manager types to make the loading process easy to extend
  4. Integrating levels to make the game interesting
  5. Building the bomb (alternative weapon)
  6. Implementing CPU controlled ships
  7. Making the game informative with text
  8. Construction of a text control for displaying an intro

Before we touch any of these topics, we should have a look at the current state of the game.

Background

This article is a follow up article on SpaceShoot - A multiplayer game in HTML5, which can be found here. The whole project originated from the idea of building something using the WebSocket element, which has been introduced lately. However, it was obvious that a full featured single-player mode would also be very useful.

There will be another article about the full implementation of the multiplayer modus. This one will mostly contain C#, since the server was written with the help of Fleck, written by Jason Staten. Finally I am thinking about a mobile version as well. This one could be a multiplayer only version on Windows Phone 7 or just using responsive design techniques in HTML / CSS.

The game itself

The concept of SpaceShoot is quite simple: The player controls a spaceship which is floating in space and attacked by asteroids and other ships. The game is controlled by using the keyboard with the following keys:

The keyboard submenu that can be reached from the main menu

  • Arrow keys to accelerate (up), break (down), and steer left (left) and right (right)
  • The Space key to shoot normal ammo
  • The Ctrl key to deploy a bomb
  • The score screen can be switched on and off using the Tab key
  • The menu can be reached and left by using the Esc key

The player starts with full life and full ammo, but no bombs and no shield. In order to heal, refill ammo, get bombs, or power up the shield, the player has to collect items that are presented occasionally. Those items are limited in their presence, i.e., they vanish after a certain time. This is displayed by a degenerating progress bar below the items. These items can be collected:

  • Health Regeneration - Gives +30 health
  • Ammo pack - Gives +20 ammo
  • Additional bombs - Gives +2 bombs
  • Shield powerup - Gives full shields

Collecting items will give points to the player. Also it is important to know that even though some packs might increase a certain property, e.g., health, ammo, or bomb count, the property will still hold certain constraints, i.e., the property's value cannot exceed its maximum. Taking health, for instance, gives us a range from 0 (dead) to 100 (maximum health). Collecting a health pack (+30 health) while having 87 health will therefore result in 100 health and not 117.

The shield is a special ability which will protect the spaceship. The shield will protect the ship by taking the damage. Its occurrence will increase with the level count. This is important since the asteroid count will also increase with the level count. Also, computer controlled drones (which are send out regularly) will be more than a pain in the higher levels. Every 15th level, a whole armada of drones is send out in order to destroy the player's ship. Those armadas grow proportional to the level count.

The computer controlled ships try to fly directly in the direction of the player's ship. If the player is close enough, they will fire in a regular interval. They also fire if an asteroid is in their way. However, they do not try to make detours in order to avoid asteroids.

The code

The code did not change a lot (architecturally) since the last time. The major difference is that the code was extended in a lot of areas. It is also worth noticing that some of the improvements I mentioned last time have been included in this version. All of the major extensions and improvements will be discussed in depth in this section.

Menu implementation

The menu in action

The menu is implemented using the DOM. Here we want to make use of HTML instead of just abusing the Canvas element (after all, the styling possibilities given by CSS should not be avoided but used as often as possible). Using jQuery would give us another production boost. However, in this case, jQuery was excluded in order to develop the menu without using any external libraries. The basic HTML is build up like the following code:

HTML
<div id="menuscreen">
<p id="title">SpaceShoot</p>
<ul id="menu-main">
...
</ul>
<ul id="menu-scr">
...
</ul>
...
</div>

So we are just spawning a <div>-element containing all of the menus. In the CSS, we are applying some fancy styling in order to keep everything nice and harmonic. The different menu entries that will be shown are presented using <ul>-lists. Let's find out how the events will be applied in our JavaScript code:

JavaScript
menu.init = function() {
    document.getElementById('menu-sp').onclick = function() {
        game.startSingle();
        menu.toggle();
    };

    document.getElementById('menu-setname').onclick = function() {
        menu.select('menu-user');
    };

    // Others ...
};

All menu functions are placed in an object called menu. The init() method will set up the menu, i.e., set the right submenu visible and set all click callbacks for further usage. While some elements have a direct click-event like starting the single player (and then closing the menu), others open other submenus. The opening is done by menu.select(id). Here the following code is executed:

JavaScript
menu.select = function(id) {
    var m = document.getElementById('menuscreen');
    var items = m.getElementsByTagName('ul');
    
    for(var i = items.length; i--; ) {
        var isel = items[i].id === id;
        items[i].style.display = isel ? 'block' : 'none';
    }
};

The method just opens the menu and gets all underlying submenus. Then all those submenus will be hidden, except the one that should be selected (shown). With jQuery, such a command could have been written in one short line. The toggling of the menu (showing or hiding) is being done by the toggle() method. This one has been implemented in the following way:

JavaScript
menu.toggle = function() {
    var m = document.getElementById('menuscreen');
    var im = document.getElementById('inactive')
    if(m.style.display === 'block') {
        game.running = true;
        m.style.display = 'none';
        im.style.display = 'none';
        c.canvas.focus();
    } else {
        game.running = game.running && game.multiplayer;
        m.style.display = 'block';
        im.style.display = 'block';
        menu.select('menu-main');
        m.focus();
    }        
};

The method checks if the menu is currently shown (over the display rule of the menu screen <div>). It then executes the right statements depending on the visibility status. The <div>-element with the ID inactive is just a layer that will be placed between the menu and the canvas / body of the document. Therefore the rest of the document will have a look which indicates that the focus is now on the menu and that the menu has to be closed in order to continue with the rest.

Over the menu it is easily possible to set some options

With CSS styling, it is easily possible to adjust the design of text boxes and headers in the menu. Since our JavaScript design is to invoke each button with a (more or less) unique event, we can set up an event to catch the save settings link with an update of the local settings. All settings are stored in the localStorage object. This is a very useful concept that will be explained now.

Using LocalStorage

Showing highscores stored by the LocalStorage object

In order to save the settings and in order to save personal high-scores, we have to deal with cookies or other techniques. Using cookies is not a good idea, since those are not only limited, but also kind of annoying to use. The localStorage object is the solution to most problems encountered with cookies. In order to limit access to the object (and therefore to limit JSON conversions), we buffer the current settings, as well as the current high-score in some local variables.

When the application loads, the previous high-scores will be loaded into a local array using the localStorage object and JSON. Unfortunately, we can only save variables of type DOMString in the storage. However, this means that by using JSON, we can store all JavaScript objects. The loading sequence is defined in the code as follows:

JavaScript
score.init = function() {
    var s = JSON.parse(localStorage.getItem('highscores'));    
    if(s)
        score.scores = s;
};

Now this is not very complicated. We get the saved high-scores from the localStorage and save them in the local buffer if there were previous high-scores saved (this is for sure not the case on first time loading). Otherwise, score.scores is still just an empty array since the local variable s will be undefined and therefore false.

The current score screen can always be switched on or off within the game by pressing the TAB key. Once the game is over, the score screen is automatically shown. An example score screen (with all possible statistics) is shown below.

The current score can be viewed within the game at any time

In order to update the high-score list after a game, score.update() is being called. This one will check if the current score is a new high-score. If this is the case, a modal message dialog is shown. In any case, the current score is added to the list of scores. Additionally, the localStorage object is updated with the current list of scores in order to keep the scores up to date.

JavaScript
score.update = function() {
    var dd = new Date();
    var pts = myship.points;
    if(pts > score.high().points) {
        //Show Message!
    }
    score.scores.push({ date : dd, points : pts, level: game.level, player : settings.playerName });
    localStorage.setItem('highscores', JSON.stringify(score.scores));
};

Image and audio managers

As stated in the previous article, we need a more powerful manager for loading and obtaining images. Since the game requires audio in some scenarios, another manager for audio objects was also required. This final single-player code contains an object resourceManager, which creates instances of imageManager and audioManager to handle sprites, logos, and sound snippets. The resource manager object has been declared like this:

JavaScript
var resourceManager = new function() {
    this.sounds = [
        //Name of sound files in audio directory without .wav
    ];
    this.sprites = [
        //Name of image files in img directory without .png
    ];
    this.done = 0;
    this.total = this.sounds.length + this.sprites.length;
    infoTexts.push(new infoText(c.canvas.width / 2, c.canvas.height / 2, 
                   100, "Loading 0%", secondaryColors[0]));
    draw();
};

We need only to place the name of the files without ending and the directory in there. To inform the user that the loading process has started, an info text is placed in the middle of the screen. The rest is done by executing the init() method:

JavaScript
resourceManager.init = function() {
    soundEffects = new soundManager(resourceManager.sounds, resourceManager.callback);
    spriteSheets = new imageManager(resourceManager.sprites, resourceManager.callback); 
};

So basically all managers are just started in there. One thing to notice right away is that we not only pass the array with the objects in, but also a callback method. That method will handle all the magic, determining if all images are loaded and writing some useful output to the screen in order to inform the user.

JavaScript
resourceManager.callback = function() {
    resourceManager.done += 1;
    infoTexts.splice(0, 1);
    
    if(ready = resourceManager.done === resourceManager.total)
        resourceManager.ready();
    else
        infoTexts.push(new infoText(c.canvas.width / 2, c.canvas.height / 2, 100, 
            "Loading " + parseInt(100 * resourceManager.done / resourceManager.total) + "%", 
            secondaryColors[0]));
    
    draw();
};

In the case where all external files (images and audio) are loaded, the ready() method is called. This is the one that will perform the closing tasks. In our case, the most important call there is to set up the network. This will not do anything in the current code (single-player). However, in the third article, this will gain some importance (and it will look more like in the first article where we implemented a very simple multiplayer).

JavaScript
resourceManager.ready = function() {
    //Set other unimportant tasks
    network.setup();
};

Since the imageManager object works as explained last time, we will have a look at the audioManager this time. There are some major differences between this manager and the one for images. First, let's take a look at the code:

JavaScript
var soundManager = function(sounds, callback) {
    this.soundNames = sounds;
    this.sounds = [];
    this.count = sounds.length;
    
    for(var i = 0; i < this.count; i++) {
        var t = document.createElement('audio');
        t.preload = 'auto';
        t.addEventListener('loadeddata', function() {
            callback();
        }, false);
        t.src = 'sounds/' + this.soundNames[i] + '.wav';
        this.sounds.push([t]);
    }
};

The constructor just takes the array with sound names and the method to call back when a sound file has been loaded successfully. In order to achieve this, we set various options. One is to use the automatic preload. Another is to bind the event listener to the loadeddata event. This will trigger the callback method once the whole audio snippet is loaded. The load event that is known from the imageManager is not applicable here. At the end, the sound file is added to an array (of <audio> tags) that is kept by the local manager object.

The next thing we look at is the get() method. All methods in the code can access the external files (placed in the appropriate tags) over the get() method. Let's look at it:

JavaScript
soundManager.prototype.play = function(name) {
    if(settings.playSounds)
        for(var i = this.count; i--; ) {        
            if(this.soundNames[i] === name) {
                var t = this.sounds[i];
                
                for(var j = t.length; j--; ) {                    
                    if(t[j].duration === 0)
                        return;
                    
                    if(t[j].ended)
                        t[j].currentTime = 0;
                    else if(t[j].currentTime > 0)
                        break;
                        
                    t[j].play();
                    return;
                }
                
                var s = document.createElement('audio');
                s.src = t[0].src;
                t.push(s);
                s.play();
                return;
            }
        }
};

Now that looks fancy! Why are we using so much code? Wouldn't a simple this.sounds[i].play() followed by a return statement do the job? The answer here is obviously no - however, the reason is interesting: The audio tag (luckily) cannot play multiple times or be mixed. At the moment, Google is doing a lot of coding there in order to come up with another tag to overcome all those issues, i.e., making the HTML standard more suitable for audio in games. Since most of their implementations are restricted to Google Chrome and since all of those experiments are still in a very early stage, we tried to overcome this limitation with this workaround.

Basically, the code just looks if all fitting audio tags is already playing the snippet. If it is, a clone tag will be created and added to the array. This fresh clone tag will then play the sound snippet. A tag is fitting if the sound name is equal to the requested name. Images can be used multiple times, so this work around is only necessary for the audioManager.

Including items and levels

In order to make the game more interesting, it is necessary to give the player something to be proud of: like levels or items (refreshing health, ammo, or others). All those things have been implemented in the game loop and will be called at the end of the circle before the draw() method. Let's have a short look at the method to be called:

JavaScript
var items = function() {
    //...

    if(game.ticks % 200 === 0) {
        //Next Level
        game.level++;
        //START DRONE WAVE if Level == 15, 30, ...
    }

    //Give certain levels (2, 5, 10, 25, 50, 75, ...) something interesting
    var gt = (game.ticks * game.level);

    //Generate an asteroid
    if(gt % 100 === 0)
        generateAsteroid();

    //Generate an AI controlled drone
    if(gt % 500 === 0)
        generateDrone();
    
    //Cool Items!
    if(game.ticks % 50 === 0) {
        var coin = Math.random();
        
        if(coin < 0.1)
            generateHealthpack();
        else if(coin < 0.2)
            generateAmmopack();
        //...
    }
};

A cool possibility has been added with the bomb packs. When having a bomb pack (consisting of two bombs), it is possible to deploy one of those bombs. They will detonate after four seconds.

More possibilities

Placing a bomb and watching it detonate is a lot of fun

Since just flying around with just one weapon is really boring, it is necessary to include some other (fancy) weapon. In this case, we pick a completely different one: the bomb. The characteristic is quite simple: the player does not shoot the bomb - instead he deploys it. The bomb does not detonate instantly, it gives the player some time to escape from the bomb's detonation radius. This is mandatory since the bomb will also damage the player. The damage of the bomb is calculated by a square formula. This is because the damage of the bomb is inverse proportional to the area it covers. The area is a circle in this case, i.e.,we have some square relation to the bomb's damage radius.

Several extensions are required in order to include another weapon. One extension is another item. This is implemented the same way that ammo packs and other packages have been included. Another requirement is that the ship has an attribute that contains the number of bombs. Also the ship's logic has to look if some key is pressed and deploy a bomb - if the player has at least one bomb.

Set up an AI

Each drone makes several calculations - first with (all) asteroids and then with the player's ship

The computer controlled drones have to be intelligent (do not be afraid - it is not Skynet time yet!). In order to save some important CPU cycles and in order to make the game still enjoyable, we implement a really rudimentary method in order to keep the movements in an obtrusive way. Our logic follows these simple conditions:

  1. If an asteroid is directly in the flying path (and closer than a certain distance), the drone has to shoot
  2. Calculate the angle to fly to the player's ship
  3. If the angle is bigger than a certain tolerance, start rotating
  4. Otherwise if the player's ship is below a certain distance, then shoot it

This has been implemented like the following:

JavaScript
drone.prototype.logic = function() {
    //Set up variables and calculate some tolerances
    var ta = d2g(this.angle); //degrees to grad of current angle

    //Loop over all asteroids
    for(var i = asteroids.length; i--; ) {
        t1 = asteroids[i].x - this.x;
        t2 = asteroids[i].y - this.y;
        d = Math.sqrt(t1 * t1 + t2 * t2);
        beta = Math.acos((Math.sin(ta) * t1 + Math.cos(ta) * t2) / d);
        
        //The drone can shoot and it should (angle is OK and distance is OK) shoot
        if(this.cooldown === 0 && beta < tol2 && d < bomb2) {
            this.cooldown = DRONE_INIT_COOLDOWN;
            particles.push(new particle(this.x, this.y, 
              (3 + this.speed) * Math.sin(ta), - (3 + this.speed) * Math.cos(ta), this));
            //LEAVE iteration
            break;
        }
    }

    //Now let's have a look for the player's ship
    var f = this.x > ships[0].x ? 1 : -1;
    t1 = this.x - ships[0].x;
    t2 = this.y - ships[0].y;
    d = Math.sqrt(t1 * t1 + t2 * t2);
    beta = Math.acos((Math.sin(ta) * f * t1 + Math.cos(ta) * t2) / d);

    //What to do with those numbers?
    if(beta > tol) {
        this.angle = this.angle + f * ROTATE;
    } else if(this.cooldown === 0 && d < MAX_BOMB_RADIUS) {
        this.cooldown = DRONE_INIT_COOLDOWN;
        particles.push(new particle(this.x, this.y, 
          (3 + this.speed) * Math.sin(ta), - (3 + this.speed) * Math.cos(ta), this));
    }

    //...
};

This AI is just a funny start and has some silly consequences. On the one hand the drones will certainly be a pain in the neck (consider 150 drones streaming in at level 150), on the other side this will also help the player to destroy asteroids. This funny side effect has also been displayed on a blocking info text that will display on every wave of drones. More on that later.

Fading texts

Fading text after picking up a health package

In order to display small information texts in an unobtrusive and cool way, we have to introduce a new object: infoText. The basic constructor is very simple and has the following source:

JavaScript
var infoText = function(x, y, time, text, color) {
    this.x = x;
    this.y = y;
    this.time = time;
    this.total = time;
    this.text = text;
    this.color = color;
};

Basically, we just set the (center) position of the text as well as the color and the time it has. We store the time twice in order to perform decrements on one time and still have the original value. This is then used in order to determine the current alpha status of the color, where 1 is the starting value and 0 is the final value. The drawing is performed in the draw() method:

JavaScript
infoText.prototype.draw = function() {
    c.save();
    c.translate(this.x, this.y);
    c.textAlign = 'center';
    c.fillStyle = 'rgba(' + this.color + ', ' + (this.time / this.total) + ')';
    c.fillText(this.text, 0, 0); 
    c.restore();
};

Blocking text

The intro uses blocking text in order to display the cool story

The fading text is a nice feature, but it is not suitable for an intro or a credit screen or something else that has to block the game's logic. An additional point for another type of text is that some texts should slowly be displayed in order to avoid giving the player a screen full of letters. The solution is the introText object that has been set up in the following way:

JavaScript
var introText = function(sx, sy, maxwidth, maxheight, lineheight, textArray) {
    this.fulltext = textArray;
    this.currentline = 0;
    this.currentindex = 0;
    this.lines = textArray.length;
    this.linelength = textArray.length > 0 ? textArray[0].length : 0;
    this.text = [''];
    this.font = '20px Orbitron';
    this.fillcolor = 'rgb(255, 255, 255)';
    this.strokecolor = 'rgb(0, 0, 0)';
    this.x = sx;
    this.y = sy;
    this.lineheight = lineheight;
    this.width = maxwidth;
    this.height = maxheight;
    this.fadetime = 50;
};

Here we see that a lot of properties are actually set up. The constructor call basically includes some text to be shown at some starting position (x and y) as well as the maximum width and maximum height of the text. Also, the height of the line has to be specified. It is important to note that the different lines have to be split up in an array of text, where each entry contains one line of text.

Right now the canvas is not as convenient as the text drawing methods in GDI+ (included in the .NET Framework). One drawback is that it does not automatically include line breaks or options for specifying the maximum width of a text. The only option that we have right now is to measure the text and reduce the amount of characters in a line depending on the result of the text measurement.

The code will do the following:

  1. Is the current line finished? Then go to the next line.
  2. Is there no next line? Then start fading away.
  3. Calculate the current index and add the next word.
  4. If the width of the text from the beginning to the next word is bigger than the line, then start a new display line.
  5. Append the next letter to the current display line.
  6. Increment the position index.

The code will work if at least one line of text has been passed as an argument. Most of the code can for sure be used without the effect of displaying character by character. Basically, it could be used in a loop in order to display some text in a box on a <canvas> element.

JavaScript
introText.prototype.logic = function() {
    if(this.currentindex === this.linelength) {
        this.currentline += 1;
        this.text.push('');
        this.currentindex = 0;
        
        if(this.currentline < this.lines)
            this.linelength = this.fulltext[this.currentline].length;
    }

    if(this.currentline === this.lines)
        return --this.fadetime;

    var idx = this.text.length - 1;
    var text = this.text[idx];
    var line = this.fulltext[this.currentline];
    var chr = line[this.currentindex];
    var next = line.indexOf(' ', this.currentindex);
    var plus = '';

    if(next > 0)
        plus = line.substring(this.currentindex, next);
    else if(next < 0)
        plus = line.substring(this.currentindex);

    c.save();
    c.font = this.font;

    if(c.measureText(text + plus).width > this.width)
        this.text.push(chr);
    else
        this.text[idx] = text + chr;

    c.restore();
    this.currentindex += 1;
    return true;    
}; 

Integrating social connectors

A completely new feature is the social bar in the main menu. Please note that this is not a new thing in general: it is just new feature for SpaceShoot (compared to the original article). Such connectors may help any game to become known, since they allow people to share the Website with no effort at all. Let's have a look at the HTML to include the (very basic) social plugins of Facebook, Google+ and Twitter:

HTML
<div id="promo">
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://html5.florian-rappl.de/SpaceShootSingle/" data-lang="en" data-count="vertical">Tweet</a>
<div class="g-plusone" data-size="tall" data-href="http://html5.florian-rappl.de/SpaceShootSingle/"></div>
<div class="fb-like" data-href="http://html5.florian-rappl.de/SpaceShootSingle/" data-send="false" data-layout="box_count" data-width="60" data-show-faces="false" data-action="like"></div><div id="fb-root"></div>
</div>
</div>

This is the code that one should officially include in the website in order to make the JavaScripts work. In order to have those external JavaScripts on a combined place we just locate them in a file called promo.js. This file contains the following snippet of JavaScript:

JavaScript
!function(d,s,id) {
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)) {
js=d.createElement(s);
js.id=id;
js.src="http://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}}(document,"script","twitter-wjs");
(function(d, s, id) {
var js,fjs=d.getElementsByTagName(s)[0];
if(d.getElementById(id))
return;
js=d.createElement(s);
js.id=id;
js.src="http://connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js,fjs);
}(document, 'script', 'facebook-jssdk'));
(function() {
var po=document.createElement('script');
po.type='text/javascript';
po.async=true;
po.src='https://apis.google.com/js/plusone.js';
var s=document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();

The source code has been modified slightly. However, the main purpose did not change at all. Each of those scripts behaves similarly: They all create a script tag and set the appropriate target source. One remarkable thing here is that only Google's script performs an async operation (if available). This is something that is missing on the other scripts and should be included in the final version of SpaceShoot (or any website).

Points of interest

In order to make the game work perfectly together with a server, some changes in the code are required. Those changes will be discussed in detail in the next article. Most of those changes imply custom generation of objects like asteroids, ships, and others, as well as changes in the game loop. The live version also contains some social integrations in order to be shared more easily and some methods to make cheating in the single-player a little bit harder.

The full featured single-player can be viewed live at http://html5.florian-rappl.de/SpaceShootSingle/.

My personal highscore

The screenshot shows my personal high-score. It should be noted that this is not the best high-score overall - one colleague of mine went up to level 251 and scored over 190000 points. What will most probably kill a player in such high levels are not asteroids any more, but the (waves of) drones. Fighting around 180 drones at once is close to being impossible without a lot of health, bombs, ammo, and active shields. Good luck to everyone trying!

This is the second article based on the SpaceShoot game. The first one can be viewed here on CodeProject at http://www.codeproject.com/Articles/314965/SpaceShoot-A-multiplayer-game-in-HTML5.

Browser issues

According to official sources the game should work fine on all current browsers (IE 9, Chrome 17, Safari 5.1, Opera 11.6 and Firefox 10). However, it seems like all of those browsers did implement the <audio> tag, but not all of the specified events. Therefore you might experience some issues by using one of those browsers.

The game has been mainly developed by using Opera. A lot of tests have been executed on Google Chrome as well - so those two might be the safest options. If you experience trouble with any current browser (including Opera and Chrome) feel free to report them in the comments as soon as possible.

History

  • v1.0.0 | Initial release | 12.02.2012.
  • v1.1.0 | Update with social connectors | 14.02.2012.
  • v1.1.1 | Update with browser issues | 15.02.2012

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
Germany Germany
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.

During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics he is working as a senior technical consultant in the field of home automation and IoT.

Florian has been giving lectures in C#, HTML5 with CSS3 and JavaScript, software design, and other topics. He is regularly giving talks at user groups, conferences, and companies. He is actively contributing to open-source projects. Florian is the maintainer of AngleSharp, a completely managed browser engine.

Comments and Discussions

 
GeneralMy Vote 5 Pin
Shemeemsha (ഷെമീംഷ)6-Oct-14 19:57
Shemeemsha (ഷെമീംഷ)6-Oct-14 19:57 
GeneralRe: My Vote 5 Pin
Florian Rappl7-Oct-14 10:38
professionalFlorian Rappl7-Oct-14 10:38 
GeneralMy vote of 5 Pin
vinayakJJ23-Aug-13 22:55
vinayakJJ23-Aug-13 22:55 
GeneralRe: My vote of 5 Pin
Florian Rappl24-Aug-13 23:41
professionalFlorian Rappl24-Aug-13 23:41 
GeneralPretty cool Pin
Espen Harlinn1-Aug-13 16:08
professionalEspen Harlinn1-Aug-13 16:08 
GeneralRe: Pretty cool Pin
Florian Rappl1-Aug-13 19:53
professionalFlorian Rappl1-Aug-13 19:53 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun12-Jun-13 2:36
Humayun Kabir Mamun12-Jun-13 2:36 
GeneralRe: My vote of 5 Pin
Florian Rappl12-Jun-13 3:04
professionalFlorian Rappl12-Jun-13 3:04 
GeneralMy vote of 5 Pin
Savalia Manoj M9-Jan-13 16:36
Savalia Manoj M9-Jan-13 16:36 
GeneralRe: My vote of 5 Pin
Florian Rappl12-Jun-13 3:03
professionalFlorian Rappl12-Jun-13 3:03 
GeneralMy vote of 5 Pin
DotNetMastermind24-Nov-12 12:52
DotNetMastermind24-Nov-12 12:52 
GeneralRe: My vote of 5 Pin
Florian Rappl24-Nov-12 22:42
professionalFlorian Rappl24-Nov-12 22:42 
GeneralMy vote of 5 Pin
james_berry19-Mar-12 15:48
james_berry19-Mar-12 15:48 
GeneralRe: My vote of 5 Pin
Florian Rappl24-Nov-12 22:41
professionalFlorian Rappl24-Nov-12 22:41 
Questiondon't know how I missed this, its freekin cool Pin
Sacha Barber18-Mar-12 22:39
Sacha Barber18-Mar-12 22:39 
AnswerRe: don't know how I missed this, its freekin cool Pin
Florian Rappl18-Mar-12 23:13
professionalFlorian Rappl18-Mar-12 23:13 
QuestionAwesome Pin
Shivprasad koirala7-Mar-12 15:58
Shivprasad koirala7-Mar-12 15:58 
AnswerRe: Awesome Pin
Florian Rappl7-Mar-12 22:33
professionalFlorian Rappl7-Mar-12 22:33 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey27-Feb-12 3:33
professionalManoj Kumar Choubey27-Feb-12 3:33 
GeneralRe: My vote of 5 Pin
Florian Rappl27-Feb-12 4:12
professionalFlorian Rappl27-Feb-12 4:12 
GeneralMy vote of 5 Pin
Harm-Jan24-Feb-12 1:37
professionalHarm-Jan24-Feb-12 1:37 
Very nice indeed!

A question though.
I noticed you use the object keyboard or control to control the ship.

On a tablet due to the absence of a keyboard one cannot play the game.

Is control a future extension for that?
GeneralRe: My vote of 5 Pin
Florian Rappl24-Feb-12 1:56
professionalFlorian Rappl24-Feb-12 1:56 
GeneralMy vote of 5 Pin
Bojan Banko23-Feb-12 4:44
Bojan Banko23-Feb-12 4:44 
GeneralRe: My vote of 5 Pin
Florian Rappl23-Feb-12 5:39
professionalFlorian Rappl23-Feb-12 5:39 
GeneralMy vote of 4 Pin
Jasmine250120-Feb-12 12:50
Jasmine250120-Feb-12 12:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.