Click here to Skip to main content
11,433,220 members (63,004 online)
Click here to Skip to main content

Game development using Dart

, 4 Apr 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
How to implement a simple HTML5 game using Dart.

Introduction

In this article I will explore the Dart language by attempting to implement a simple web-based game using it. I've uploaded a video that demonstrates the game-play, and this JsFiddle will let you try it (or at least a limited version of the game).

Note: The instructions in the game says to use the arrow keys to move, this is incorrect, that should be W, A, S and D. Sorry. Here's a fixed JsFiddle: http://jsfiddle.net/ZpLAu/3/.

For those of you who are unfamiliar with Dart it's a language developed by Google that is currently being ECMA standardized. It lends itself both to web and server-side development.

I'll go through two projects, one very simple were the aim is just to get something on screen that can be controlled by the user and a more complex game project where "a full game" is the intended result. While I will put some effort into implementing a game the main purpose of this exploration is to examine Dart, not to produce the best game ever.

The aim of this article will therefore be two-fold;

  • 1. Show basic syntax and structure of a Dart web application.
  • 2. Demonstrate a simple game.

Because these are two very different things the article might come off as a bit unfocused at time, for this I apologize in advance.

Background

Why Dart

From a personal perspective I picked Dart for two reasons;

  • I wanted to learn another language.
  • I wanted to do something web-based as I rarely get to do that at work.

While I can do a bit of JavaScript I also wanted to see if Dart delivers on its promise of Structured web apps as with my background from C++, Java and C# I sometimes find JavaScript a bit peculiar to work with and I would not mind a language the allows me to do what JavaScript does but in a way I am more familiar with.

Why create Dart in the first place?
According to the FAQ on the dartlang.org web-site Google has written their share of web apps and think they know a bit or two about things that could improve in the way web apps are developed. Dart aims to fix some things with JavaScript and because of that it can be seen as a replacement for JavaScript, I think that is unlikely to happen but having more than one tool (or in this case, language) can only be a good thing. I would hate having to use C# for a task that is better suited for C++ and vice versa so I welcome the choice.

Seth Ladd (who works on Dart) posted a list of 16 points as to why he likes Dart in an answer to the question "Does Dart have any useful features for web programmers?", and I think the points are very valid and agree with them, it's a good summary why I like the look of Dart;

  • Optional static types. When I'm prototyping or simply writing small scripts, I don't use a ton of static types. I just don't need 'em, and I don't want to get bogged down with the ceremony. However, some of those scripts evolve into bigger programs. As the scripts scale, I tend to want classes and static type annotations.
  • Innocent until proven guilty. Dart tries hard to minimize the situations that result in a compile-time error. Many conditions in Dart are warnings, which don't stop your program from running. Why? In keeping with web development fashion, it's imperative to allow developers to try a bit of code, hit reload, and see what happens. The developer shouldn't have to first prove the entire program is correct before just testing a corner of the code.
  • Lexical scope. This is awesome, if you're not used to it. Simply put, the visibility of variables, and even this, is defined by the program structure. This eliminates a class of puzzlers in traditional web programming. No need to re-bind functions to keep this to what you think or expect.
  • Real classes baked into the language. It's clear most developers want to work in classes, as most web development frameworks offer a solution. However, a "class" from framework A isn't compatible with framework B, in traditional web development. Dart uses classes naturally.
  • Top-level functions. One painful part of Java is that everything has to be put into a class. This is a bit artificial, especially when you want to define a few utility functions. In Dart, you can define functions at the top level, outside of any class. This makes library composition feel more natural.
  • Classes have implicit interfaces. The elimination of explicit interfaces simplifies the language. No more need to define IDuck everywhere, all you need now is a class Duck. Because every class has an implicit interface, you can create a MockDuck implements Duck
  • Named constructors. You can give constructors names, which really helps with readibility. For example: var duck = new Duck.fromJson(someJsonString)
  • Factory constructors. The factory pattern is quite common, and it's nice to see this baked into the language. A factory constructor can return a singleton, an object from a cache, or an object of a sub-type.
  • Isolates. Gone are the days of sharing mutable state between threads (an error prone technique). A Dart isolate is an isolated memory heap, able to run in a separate process or thread. Isolates communicate by sending messages over ports. Isolates work in the Dart VM and can compile to Web workers in HTML5 apps.
  • Dart compiles to JavaScript. This is critically important, as JavaScript is the lingua franca of the web. Dart apps should run across the modern web.
  • Strong tooling. The Dart project also ships an editor. You'll find code completion, refactoring, quick fixes, code navigation, debugging, and more. Also, IntelliJ has a Dart plugin.
  • Libraries. You can organize Dart code into libraries, for easier namespacing and reusability. Your code can import a library, and libraries can re-export.
  • String interpolation. This is just a nice feature, making it easy to compose a string: var msg = "Hello $friend!";
  • noSuchMethod. Dart is a dynamic language, and you can handle arbitrary method calls with noSuchMethod().
  • Generics. Being able to say "this is a list of apples" gives your tools much more info to help you and catch potential errors early. Luckily, though, Dart's generics are more simple that what you're probably used to.
  • Operator overloading. Dart classes can define behavior for operators like + or -. For example, you could write code like new Point(1,1) + new Point(2,2).

Note: I am in no way saying Dart is better than JavaScript, I haven't worked enough with either of them to be able to make that judgement, this exploration is merely to see if it is for someone with my background easier to produce a specific web-app using Dart rather than JavaScript.

Using the code

The project download for this article is a DartEditor project, to open it just import it by selecting File and then Open existing folder. Personally I prefer working in a "full" version of Eclipse, but since the DartEditor is a little bit more convenient for someone who just wants to have a look I've left the project as a DartEditor project.

The DartEditor download is essentially a version of Eclipse limited to Dart development.

For those who rather use Eclipse there's an Eclipse plugin, and if that's what you prefer then get the plugin from Dart eclipse plugin update site.)

Dart runs in a Dart Virtual Machine, but since not many people have a browser that can run that, it can also be compiled into JavaScript.

Dart

I'll try to explain and cover most of the Dart syntax used in this article, but if you want a quick Dart primer then the Darrrt sample app is a good place to start. It goes through setting up a skeleton app, creating a HTML page that the Dart code can interact with and creating a simple text manipulation class.

The Basics

The initial "game" is simply a black circle that the user can move using the arrow-keys on the keyboard. Simple enough to start with and I'll use that to demonstrate some Dart syntax and also show the required parts for getting a simple web-game up and running.

The page

As with any web app we need a HTML page to host our content;

<html>
   <head>
      <meta charset="utf-8"/>
          <title>Basics</title>
   </head>
  <body>
    <canvas id="area" width="800px" height="600px"></canvas>
    <script type="application/dart" src="basics.dart"></script>
  </body>
</html>   
    

Simple enough.
The canvas is the "host" for the game, and it is using its context the game will render its graphics. The script element is of course used the same way it would be for JavaScript and point to the basics.dart file.

The .dart-file

As Dart is supposed to provide a structured way of developing apps it comes with libraries (and you are obviously free to develop your own libraries as well) and Dart uses a import syntax not unlike that of languages such as Java and C#, at the top of basics.dart the following imports are found;

import 'dart:html';
import 'dart:math';
import 'dart:async';
import 'dart:collection';
    

Pretty self-explanatory what that brings to the project, and neatly presented.

A Dart file can have a main entry point, just like the static void Main(String[] args) in C#, and in my game I'll use that to get a reference to the canvas and start the main game-loop.

/* This is the main entry point, obviously */
void main() {
  final CanvasElement canvas = querySelector("#area");
  canvas.focus();
    scheduleMicrotask(new GameHost(canvas).run);
}
    

To var or not to var

Dart, like many other languages, allows using var or the actual type when declaring a variable. I normally prefer using var in cases where it means the same as the actual type but I am a bit conflicted in Dart. I like communicating intent by declaring things as final, and while Dart allows me to do;

final CanvasElement canvas = querySelector("#area");

It does not allow me to do;

final var canvas = querySelector("#area");

Because of this, I'm going to be mostly using specific types throughout this article's code. Also, in some cases the intelli-sense seems to be working better when I specify the type explicitly and that's always nice.

The method querySelector that returns the Element is part of dart:html and as Dart isn't a object-oriented in the strict way C# is there's no class required, it's simply a public method in that library.

The method scheduleMicrotask is part of dart:async and is used to run a method asynchronously, in this case the run method on the GameHost class.

Calling .focus() attempts to move focus away from the address bar of the browser, this does not always work but sometimes it does. The aim of doing this is to allow the arrow-keys to control the black circle without the need of first manually clicking the div.

Keyboard class

In "normal" programming an event-based system is usually employed to read input (mouse over, mouse enter, key down, etc.), but in game programming it's often more convenient to consider a state that is the sum of all inputs at a given point in time. As Dart supports the event driven approach I decided to create a class that wraps keyboard events and makes them available as a state;

class Keyboard {
  final HashSet<int> _keys = new HashSet<int>();
  Keyboard() {
    window.onKeyDown.listen((final KeyboardEvent e) {
          _keys.add(e.keyCode);
    });
    window.onKeyUp.listen((final KeyboardEvent e) {
      _keys.remove(e.keyCode);
    });
  }
  isPressed(final int keyCode) => _keys.contains(keyCode);
}   
    

This class shows how similar Dart can be to Java and C#, generics works the same way (more or less, at least it looks the same), a class is defined the same way, events are wired up with function pointers (in this case to anonymous function but it could as well have been a member function), and there's support for properties. This is what I was looking for; a language that feels like Java or C# but that can do JavaScript stuff for me (ignore the fact that I need to compile this to JavaScript to get it to run in most browsers for now).

So while the bulk of this class is obvious to a Java or C# developer there are some subtleties to it;

  • The _keys variable is private, not by default but by the fact that it is prefixed with _.
  • The window variable used to wire the event listeners is a global variable in dart:html.
  • The getter isPressed's body is a single statement so the => syntax is enough, also the return value is defined by the result of the expression.
  • My code is littered with final modifiers (on members, parameters and variables), this is normal in Java but might look strange to a C# developer where readonly is only valid on members.
  • window.onKeyDown.listen takes a delegate to a method taking a KeyboardEvent as a single argument, in Java this would normally be done by a type and an anonymous implementation of that interface.

This class provides the ability to at any point query the Keyboard if a particular key is pressed and acting upon that fact by doing something like;

      if (keyboard.isPressed(KeyCode.LEFT)) 
         x = x + 10;   
   

Note that private in this case is not class-level private but library-level private, that means that anyone in the same library will have access to the _keys variable. This might seem weird but one could argue that one should know how ones own library works, rather than having to hide things from oneself.

GameHost class

In this small example the GameHost class will be responsible for the current state of the game and updating and rendering that state, a good design would split these responsibilities into different classes but that's over the top for what this basic example is trying to show.

/*
 * This class holds the state of the game (in this case just the X and Y
 * coordinates of the ball) and continuously schedules an animation frame
 * using the draw method.
 * Each iteration of the game loop goes something like this;
 *    1. Elapsed time since last frame is calculated
 *    2. State is updated based on elapsed time and keyboard state
 *    3. Current state is rendered
 *    4. A new animation frame is requested causing this to loop again
 */
class GameHost {
  final CanvasElement _canvas;
  final Keyboard _keyboard = new Keyboard();

  int _lastTimestamp = 0;
  double _x = 400.0;
  double _y = 300.0;

  GameHost(this._canvas);
  
  ...
}  
    

This first part of the GameHost shows the private member variables that is has and also it's constructor. The constructor looks strange but that's because Dart allows us to take some short-cuts. For many simple types the only thing the constructor does is assign a member variable a value passed to the constructor, in the case of GameHost we need to pass the canvas and Dart provides the this.member_name syntax to do this.

GameHost(this._canvas);
    

This is the constructor taking one argument and assigns it to _canvas, the type is implicit from the type of the member, the constructor doesn't even need an empty body defined although it could also have a body if one was needed. We could have used a normal way of defining the constructor;

GameHost(CanvasElement canvas) {
   _canvas = canvas;
}
    

but since Dart allows the short-hand version I'll try to stick to that in all cases where the constructors only do member assignment.

The double variables _x and _y are the position of the black circle that the user can move with the arrow-keys, they will be changed as part of the update each iteration of the game loop.

The game loop

Simple games usually work using a simple loop that goes something like this;

  • Read input
  • Calculate elapsed time since last frame
  • Update state based on input and elapsed time
  • Render current state
  • Go to start and do it all again

In web development we need to be told when we can render as it's up to the browser so we request an animation frame by calling window.requestAnimationFrame passing in a pointer to a method that will process the frame. In GameHost this is initially triggered by the run method;

run() {
 window.requestAnimationFrame(_gameLoop);
}

void _gameLoop(final double _) {
 _update(_getElapsed());
 _render();
 window.requestAnimationFrame(_gameLoop);
}   
    

The run methods acts as the initiator by requesting an animation frame to be handled by the _gameLoop method (which takes an double which is the frame count or something like that I think, but we don't need that for this implementation). The _gameLoop method then does the four steps of the game loop. It calculates the elapsed time by calling _getElapsed(), updates the state using _update(double elapsed), renders it render and then goes back to the start by requesting another animation frame.

There is no explicit reading of the input in that loop because that's taken care of for us using the Keyboard class shown earlier.

It is important to calculate the time step (or the elapsed time since last frame) as even though the browser will try to process the intervals are not guaranteed. This means that if things aren't updated according to the elapsed time objects will move at different velocities depending on the speed of the machine and/or browser. It's non-trivial to do a good time step, especially when details physics is used, but there are good resources that can point you in the right direction. For this article I've gone for a fairly simple approach as the physics used is simple as well.

double _getElapsed() {
 final int time = new DateTime.now().millisecondsSinceEpoch;

 double elapsed = 0.0;
 if (_lastTimestamp != 0) {
   elapsed = (time - _lastTimestamp) / 1000.0;
 }

 _lastTimestamp = time;
 return elapsed;
}   
    

The _update(final double elapsed) method in this basic example takes the current input (as captured by the Keyboard class) and adjust the position of the user's avatar. By avatar I mean a black circle. Pretty graphics is not the priority of this example.

void _update(final double elapsed) {
 final double velocity = 100.0;

 if (_keyboard.isPressed(KeyCode.LEFT)) _x -= velocity * elapsed;
 if (_keyboard.isPressed(KeyCode.RIGHT)) _x += velocity * elapsed;
 if (_keyboard.isPressed(KeyCode.UP)) _y -= velocity * elapsed;
 if (_keyboard.isPressed(KeyCode.DOWN)) _y += velocity * elapsed;
}   
    

Depending on which arrow-keys are pressed the position is adjusted horizontally and vertically by a velocity of 100.0 pixels per second (as elapsed is expressed in seconds). After input is collected and the game state mutated the last step remaining of the game loop is simply to render the state;

void _render() {
 final CanvasRenderingContext2D context = _canvas.context2D;

 context..globalAlpha = 1
        ..fillStyle = "white"
        ..beginPath()
        ..rect(0, 0, 800, 600)
        ..fill();

 context..beginPath()
        ..fillStyle = "black"
        ..arc(_x, _y, 32, 0, PI * 2.0)
        ..fill();
}   
    

The CanvasRenderingContext2D is the HTML5 canvas context, and the method applied to it should be familiar to anyone who's used the canvas before.

The double dot operator (..) is a cascade operator which allows for cascading statements as it "returns" the source object regardless of what the method or property called returns. That means that the code above is equivalent to;

context.globalAlpha = 1;
context.fillStyle = "white";
context.beginPath();
context.rect(0, 0, 800, 600);
context.fill();

...
    

Since this approach occludes any return value it cannot be used if you care about the value returned by one of the calls.

That concludes the code required for the basic example as all the steps of the game loop have been covered. The example allows the user to move around a black circle on a white background, that's it. It's not super exciting but it's allowed us to cover simple game logic as well as Dart syntax, next up is going through the implementation of a simple game I call Lost Souls, and for every implementation detail that is somehow interesting I'll cover why it's done that way.

Sample Game

The sample game implemented in this article is a very simple 2D top-down game, based on a previous article I wrote on visual concealment in games. A coworker pointed out that the rendering of the concealed areas in that article's implementation looked more like weird walls than anything else, so based on that I decided to make a game using that code but rendered to look like tall blocks or buildings.

The objective of the game is to find the Lost Souls (the red guys) and escort them to the lower right corner before the time runs out. There are pointy black things that will try to slow you down. There is a fair bit of linear algebra required for the implementation but since I've already explained that in the visual concealment article I won't touch that subject at all in this article.

There's quite alot of code required for the game but I am not going to cover every single class in detail, instead I'll cover what I think is interesting and places where some Dart-specific syntax or library are used.

Game architecture

The game consists of a series of states (GameStates) that are part of a state machine that control the general flow of the application.

The logic that "pumps" the state machine is in the GameHost class in lostsouls.dart and is responsible for tracking delta time between game updates, update game states and render. It does this by the same method as was described in the first part of the article, with the addition of being able to transition from one distinct state to another. This ability is facilitated by the fact that each state in their update method returns a state, so it is the responsibility of the state to figure out when to transition to a new state, but it is the responsibility of the "pump" to make sure the new state is made the current one. If a game state does not want to transition into a new state it simply returns itself as the "next" state.

  void draw(num _) {
    final num time = new DateTime.now().millisecondsSinceEpoch;
    if (renderTime != null)
      showFps(_, 1000 / (time - renderTime));
    renderTime = time;

    double elapsed = 0.0;
    if (lastTimestamp != 0) {
      elapsed = (time - lastTimestamp) / 1000.0;
    }

    lastTimestamp = time;
    if (currentState != null) {
      var nextState = currentState._update(elapsed);
      currentState._render();
      if (currentState != nextState) {
        currentState = nextState;
        currentState._initialize();
      }
    }

    requestRedraw();
  }

  void requestRedraw() {
    window.requestAnimationFrame(draw);
  }

The game state implementation is founded on a base class that provides access to resources most states are likely to need;

  • Keyboard, for reading keyboard state.
  • Renderer, which has helper methods for rendering.
  • AudioManager, for playing audio clips.

If also exposes the initialize, update and render methods.

class GameState {
  final Keyboard _keyboard;
  final Renderer _renderer;
  final AudioManager _audioManager;
  double _totalElapsed = 0.0;

  GameState(this._keyboard, this._renderer, this._audioManager);

  void _initialize() {
  }

  GameState _update(final double elapsed) {
    _totalElapsed = _totalElapsed + elapsed;
    return this;
  }

  void _render() {
  }
}

The GameHost object is instantiated by the main function

void main() {
  final DivElement mainDiv = querySelector("#areaMain");
  final CanvasElement canvas = querySelector("#gameCanvas");
  canvas.width = mainDiv.clientWidth;
  canvas.height = mainDiv.clientHeight;
  canvas.focus();
  scheduleMicrotask(new GameHost(canvas).start);
}

and it is the responsibility of the GameHost's constructor to feed the correct first state to the "pump"

GameHost(this.canvas) {  
  keyboard = new Keyboard();
  var renderer = new Renderer(canvas.context2D, canvas.width, canvas.height);
  currentState = new StateLoad(keyboard, renderer);
}

Note that while the Keyboard and Renderer is passed in, AudioManager is not. This is because the StateLoad state, which is responsible for loading the game's assets, is also responsible for creating the AudioManager used throughout the game. There are six different states the game can be in:

  • StateLoad; Loads the game's assets.
  • StateInit; Displays the game's name and waits for the user to press space.
  • StateNewLevel; Shows level completed messages and waits for input to start next level.
  • StateGame; Uses the GameController class to run the actual game.
  • StateGameOver; State for when the player loses a level.
  • StateFade; A utility state that can fade from one state to another.

These six states are organized into a state-machine that looks something like this:

Using individual state classes like this removes the need for a massive switch statement as a part of the main game loop. As the states can share information between them (such as level data) each state can be a fairly small, well contained class that is easy to manipulate.

The states

Load

This state is responsible for loading the audio assets the game uses (the game also uses a font asset but since I opted for rendering all my texts as HTML elements that is loaded by the CSS).

In order to play the background music and the sound effects in a timely manner these sounds need to be pre-loaded and that's why the init state takes care of that initially, before the game has started.

And while the loading state is responsible for loading the audio assets, the actual work is done by the AudioManager class (which I'll cover later), leaving the StateInit class small.

part of lostsouls;

class StateLoad extends GameState {
  AudioManager _newAudioManager;

  double _loaded = 0.0;
  bool _fullyLoaded = false;
  double _readyTime = double.MAX_FINITE;

  StateLoad(final Keyboard keyboard, final Renderer renderer) : super(keyboard, renderer, null) {
    var clips =
        [
          new SoundClip("wallhit", "audio/73563__stanestane__bunny-push.wav", false),
          new SoundClip("anchormanwallhit", "audio/124382__cubix__8bit-snare.wav", false),
          new SoundClip("lostsouldsaved", "audio/153445__lukechalaudio__8bit-robot-sound.wav", false), //1.346s
          new SoundClip("tractorbeam", "audio/211235__rjonesxlr8__explosion-15.wav", true),
          new SoundClip("levelwon", "audio/211379__rjonesxlr8__coinpickup-06.wav", false),
          new SoundClip("gameover", "audio/213149__radiy__8bit-style-bonus-effect.wav", false),
          new SoundClip("music", "audio/166393__questiion__lost-moons-serious-as-an-attack-button.wav", true),
          new SoundClip("spottedbylostsoul", "audio/211325__rjonesxlr8__powerup-13.wav", false)
        ];

    _newAudioManager = new AudioManager(new AudioContext(),  clips, _onAudioLoaded, _onAllAudioLoaded);
  }

  void _onAudioLoaded(final double loaded) {
    _loaded = loaded;
  }

  void _onAllAudioLoaded(final List<SoundClip> buffers) {
  }

  GameState _update(double elapsed) {
    super._update(elapsed);

    if (_fullyLoaded)
      return new StateInit(_keyboard, _renderer, _newAudioManager);

    return this;
  }

  void _render() {
    _renderer.clip();
    _renderer.clearAll(Colors.backgroundMain);

    final int loadedPercentage = (_loaded * 100.0).toInt();
    final bool wasFullyLoaded = _fullyLoaded;
    _fullyLoaded = loadedPercentage == 100;

    querySelector("#areaGameTextMain").text = "LOADING ${loadedPercentage}%";
    querySelector("#areaGameTextMain").style.visibility = "visible";
  }
}

The LoadState will pass a list of SoundClips, which is a helper class I added to wrap up a single piece of audio, to a newly created AudioManager that will go off and load the data asynchronously.

This means that the main responsibilities of the StateLoad is to create the AudioManager and listen to updates from it to determine how much of the resources have been loaded and to transition to the next state when every thing is loaded.

The state can do that by providing methods that the AudioManager can call back on, such a call back is defined as a typedef in Dart;

typedef void OnAudioLoaded(final double loaded);
typedef void OnAllAudioLoaded(final List<SoundClip> clips);

Using these callbacks the StateLoad can render a text showing how many percent of the assets are loaded,

querySelector("#areaGameTextMain").text = "LOADING ${loadedPercentage}%";

It would have been possible to render the text by calling the appropriate methods on the canvas, but as you get a lot for free by simply using elements of the HTML I opted for that instead.

Note that to get the percentage displayed as a whole number I need it as an int, and there is no (implicit or explicit) casting from double to int, instead Dart provides a method called .toInt().

Game

The StateGame state is the state that drives the actual game, but even that is fairly small in size as the bulk of the work is delegated to the GameController class.

Essentially, the responsibilities of the StateGame class is to monitor the GameController and initiate state transitions when some game over event/state occurs (be it the level completed or the game is lost). I let the design venture away from this well contained responsibility and ended up with the state knowing when the game is won or lost instead of the GameController, that is bad design and it shouldn't have been done like that if done properly.

For the state to know that a level is completed is counts the number of Lost Souls still active in the game, if this is zero the player has completed the level. Dart provides methods on Iterable<T> class that takes delegates, making using them very similar to LINQ (or Jinq for those who dabble in Java 8);

    if (!_controller.entities.any((e) => e is LostSoul)) {
      _audioManager.play("levelwon");
      return new StateFade(_keyboard, _renderer, _audioManager, this, new StateNewLevel(_keyboard, _renderer, _audioManager, _levelIndex + 1), 1.0, Colors.backgroundMain);
    } 

In this snippet _controller.entities is a List of Bodys and by calling .any() and passing in a delegate that checks the type of each Body the game knows if the player has saved all of them if there are none left.

Type checking is done using the is operator which works pretty much the same as is in C# or instanceof in Java.

Fade

The StateFade class is a transition state that fades out one state and then fades in the next. This is used by the StateGame state to fade over to the StateNewLevel state at the end of a won level.

    
if (!_controller.entities.any((e) => e is LostSoul)) {
  _audioManager.play("levelwon");
  final GameState nextState = new StateNewLevel(_keyboard, _renderer, _audioManager, _levelIndex + 1);
  return new StateFade(_keyboard, _renderer, _audioManager, this, nextState, 1.0, Colors.backgroundMain);
}

In the above snippet (taken from StateGame) the game checks that there are no more entities of type LostSoul, which is the win-criteria for a level, then it asynchronously plays a sound to signal that the level was won. After that the next state should be returned to let the statemachine pick it up and transition to that state, but instead of returning the state that we want to display to the user we create that state and pass it to a StateFade state along with the current state.

The StateFade state will then fade the current state out to a color, and then fade in the next state from that color.

It does this this by first rendering the state that is being faded out by calling the render() method of that state, and then rendering a coloured square on top of it with an alpha value that gradually increases. By setting the CanvasRenderingContext2D.globalAlpha to a value less than 1.0 whatever is rendered after that call is blended with what's already rendered onto the context.

  void _render() {
    // If we haven't reached _fadeTime yet, fade out current state 
    if (_totalElapsed < _fadeTime) {
      final f = _totalElapsed / _fadeTime;
      _from._render();
      _renderer..pushGlobalAlpha(f)
               ..fillFullRect(_color);
    }
    else  {
      // Otherwise fade in the next.
      final f = (_totalElapsed - _fadeTime) / _fadeTime;
      _to._render();
      _renderer..pushGlobalAlpha(1.0 - f)
               ..fillFullRect(_color);
    }
    
    // Must not forget to reset the globalAlpha or everything else
    // will also be rendered with alpha-blend.
    _renderer.popGlobalAlpha();
  }

Audio

Loading

Audio clips are loaded and maintained by the AudioManager class which takes a list of SoundClip classes and loads the audio buffer into the clip from the path specified by the clip.

As it's bad form to look up the application as it's loading resources the resources are loaded asynchronously using the dart:web_audio package.
It works like this, _loadBuffers is called from AudioManagers constructor and iterates synchronously over all SoundClips;

  void _loadBuffers() {
    for (var i = 0; i < _clips.length; ++i) {
      _clips[i]._context = _context;
      _loadBuffer(_clips[i], i);
    }
  }

Each SoundClip is given the AudioContext of the AudioManager (AudioContext being a web_audio class) and then _loadBuffer method is called for each clip. The audio buffers are loaded by sending a HTTP GET request off for the URL of the clip, and the The HttpRequest class is able to open the request asynchronously. To be notified when loading finishes the method wires up listeners to onLoadEnd and onError, these will populate the clip with the loaded data and in turn notify the classes listening to the events fired by the AudioManager when it reports progress on loading.

  void _loadBuffer(final SoundClip clip, final int index) {
    final HttpRequest request = new HttpRequest();
    request.open("GET", clip._url, async: true);
    request.responseType = "arraybuffer";
    request.onLoadEnd.listen((e) => _onBufferLoaded(request, clip, index));
    request.onError.listen((e) => _handleError(e, clip));

    try {
      request.send();
    }
    catch(ex) {
      clip._broken = true;
      _notify(clip);
    }
  }

I've opted for a fault-tolerant approach where if a sound fails to load, the SoundClip is marked as broken and will not be played even if requested to do so. That is a very lazy approach and it's probably not something you'd want to do for a real game.

Exception-handling in Dart works much the same way it does in C# or Java, and if the HttpRequest.send method fails the SoundClip is marked as broken.

When the loading of a buffer is finished the method _onBufferLoaded is called (as it's wired up to the onLoadEnd event of HttpRequest;

  void _onBufferLoaded(final HttpRequest request, final SoundClip clip, final int index) {
    _context.decodeAudioData(request.response).then((final AudioBuffer buffer) {
      if (buffer == null) {
        clip._broken = true;
      }
      else {
        clip._buffer = buffer;
      }
      _notify(clip);
    });
  }

Using the AudioContext in _context the response of the HttpRequest is asynchronously decoded into a AudioBuffer. The call to AudioContext.decodeAudioData returns a Future<AudioBuffer, a future is something that allows for something to happen in the future (obviously) or after the previous call is completed, so by doing;

    _context.decodeAudioData(request.response).then((final AudioBuffer buffer) {
   ... 
   }    

we're actually saying "decode the stream into an AudioBuffer and then do something with that buffer". In this case it's populating the _buffer of the SoundClip (or marking it as broken if the buffer comes out as null).

When decoding is complete _notify is called to sum up what has been loaded so far and report that back to the owner of the AudioManager. When all clips are loaded the loading state is free to transition to the initialization state.

Playing the clips

As the sound buffers are held as a collection of SoundClips in the AudioManager each clip is played by asking the AudioManager to play a clip by name. When the player hits a wall for example, the Player class request that the appropriate sound is played; _audioManager.play("wallhit");.

As some sounds are continuous (as the sound the Anchor Men make when pulling the player in) the audio system needs to support that in a convenient way. The approach I went for was considering some SoundClip's to be singletons, not as in a class-singleton way but as in a do not play the sound if it is already playing way. The AudioManager is unaware of whether a sound is singleton or not, that information is contained locally in each SoundClip. This means that the game can request a sound to be played effectively every update without actually getting loads of overlapping versions of the same sound playing.

Rendering

In Lost Souls there's a distinct difference between rendering of textual elements and all other elements (such as walls or the player). Texts are HTML element always present in the document and it's the responsibility of each game state to set the context of the text and toggle the visibility of the element appropriately. This is a very limiting approach compared to rendering it "manually" directly on the canvas, but it comes with the advantage of layout characteristics such as word wrapping.

All other rendering is done by drawing primitives (or sets thereof) directly onto the canvas, no pre-baked sprites or images are used in this game. The class Renderer is a collection of helper methods for drawing the things that make up the game. So to draw a filled polygon from a set of points the method fillPolygon is used;

  void fillPolygon(final List<Vector2D> polygon, final String fill) {
    final Vector2D start = polygon[0];
    context..fillStyle = fill
           ..beginPath()
           ..moveTo(start.x, start.y);

    for(var i = 1; i < polygon.length; ++i) {
      final Vector2D point = polygon[i];
      context.lineTo(point.x, point.y);
    }

    context..closePath()
           ..fill();
  }
    

As mentioned earlier, the .. operator is a cascade operator that allows the code to be a bit more concise when the same object is accessed many times.

Using the context which is a CanvasRenderingContext2D passed into the constructor of Renderer the helper methods combine functionality of CanvasRenderingContext2D to provide methods for rendering all the game's artifacts. The responsibility of rendering and how to render is still a part of each Body but it is delegated to the renderer, effectively de-coupling the Body from the CanvasRenderingContext2D (but, obviously, coupling it to Renderer.

In terms of efficiency it's not entirely clever to draw all the game's graphics using primitive method calls to the canvas, but with the number of elements drawn in this type of game is should still run at 30+ FPS on most computers.

Points of Interest

For me, implementing a game like this using Dart instead of JavaScript is more comfortable in many ways. I can use concepts I'm used to from C# and Java without feeling that the syntax is forced or contrieved (inheritance, for example) and the short distance between Dart syntax and C# allows me to be fairly efficient. This was my first ever Dart implementation and I am pretty sure I finished the code faster than what I would have in using JavaScript.

But because of how well established JavaScript is compared to how young Dart is there is a massive advantage in using JavaScript. If for nothing else than ease of Googling issues I was running into, Dart has a growing community but the online resources available are very limited to what you get if you search for the equivalent issue in JavaScript.

For those interested in Dart I recommend reading the dart style guide, it goes through a detailed list of dos and don'ts that are good to keep in mind.

History

  • 2014-04-03; First version.

License

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

Share

About the Author

Fredrik Bornander
Software Developer (Senior)
Sweden Sweden
Article videos
Oakmead Apps Android Games

21 Feb 2014: Best VB.NET Article of January 2014 - Second Prize
18 Oct 2013: Best VB.NET article of September 2013
23 Jun 2012: Best C++ article of May 2012
20 Apr 2012: Best VB.NET article of March 2012
22 Feb 2010: Best overall article of January 2010
22 Feb 2010: Best C# article of January 2010
Follow on   Google+   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 Pin
Anurag Gandhi11-Jan-15 1:09
professionalAnurag Gandhi11-Jan-15 1:09 
GeneralRe: My vote of 5 Pin
Fredrik Bornander11-Jan-15 12:13
professionalFredrik Bornander11-Jan-15 12:13 
QuestionVery nice! Pin
Florian Rappl11-Apr-14 4:31
mvpFlorian Rappl11-Apr-14 4:31 
AnswerRe: Very nice! Pin
Fredrik Bornander13-Apr-14 6:24
professionalFredrik Bornander13-Apr-14 6:24 
QuestionFinally Pin
Sacha Barber9-Apr-14 5:06
mvpSacha Barber9-Apr-14 5:06 
AnswerRe: Finally Pin
Fredrik Bornander9-Apr-14 5:26
professionalFredrik Bornander9-Apr-14 5:26 
GeneralRe: Finally Pin
Sacha Barber10-Apr-14 3:34
mvpSacha Barber10-Apr-14 3:34 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150428.2 | Last Updated 4 Apr 2014
Article Copyright 2014 by Fredrik Bornander
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid