| Index: samples/openglui/src/blasteroids.dart
|
| diff --git a/samples/openglui/src/blasteroids.dart b/samples/openglui/src/blasteroids.dart
|
| deleted file mode 100644
|
| index 203a26fe499a5e6196d56cd9a7eb3e7446fa75ee..0000000000000000000000000000000000000000
|
| --- a/samples/openglui/src/blasteroids.dart
|
| +++ /dev/null
|
| @@ -1,3334 +0,0 @@
|
| -// A Dart port of Kevin Roast's Asteroids game.
|
| -// http://www.kevs3d.co.uk/dev/asteroids
|
| -// Used with permission, including the sound and bitmap assets.
|
| -
|
| -// This should really be multiple files but the embedder doesn't support
|
| -// parts yet. I concatenated the parts in a somewhat random order.
|
| -//
|
| -// Note that Skia seems to have issues with the render compositing modes, so
|
| -// explosions look a bit messy; they aren't transparent where they should be.
|
| -//
|
| -// Currently we use the accelerometer on the phone for direction and thrust.
|
| -// This is hard to control and should probably be changed. The game is also a
|
| -// bit janky on the phone.
|
| -
|
| -library asteroids;
|
| -
|
| -import 'dart:math' as Math;
|
| -import 'gl.dart';
|
| -
|
| -const RAD = Math.PI / 180.0;
|
| -const PI = Math.PI;
|
| -const TWOPI = Math.PI * 2;
|
| -const ONEOPI = 1.0 / Math.PI;
|
| -const PIO2 = Math.PI / 2.0;
|
| -const PIO4 = Math.PI / 4.0;
|
| -const PIO8 = Math.PI / 8.0;
|
| -const PIO16 = Math.PI / 16.0;
|
| -const PIO32 = Math.PI / 32.0;
|
| -
|
| -var _rnd = new Math.Random();
|
| -double random() => _rnd.nextDouble();
|
| -int randomInt(int min, int max) => min + _rnd.nextInt(max - min + 1);
|
| -
|
| -class Key {
|
| - static const SHIFT = 16;
|
| - static const CTRL = 17;
|
| - static const ESC = 27;
|
| - static const RIGHT = 39;
|
| - static const UP = 38;
|
| - static const LEFT = 37;
|
| - static const DOWN = 40;
|
| - static const SPACE = 32;
|
| - static const A = 65;
|
| - static const E = 69;
|
| - static const G = 71;
|
| - static const L = 76;
|
| - static const P = 80;
|
| - static const R = 82;
|
| - static const S = 83;
|
| - static const Z = 90;
|
| -}
|
| -
|
| -// Globals
|
| -var Debug = {
|
| - 'enabled': false,
|
| - 'invincible': false,
|
| - 'collisionRadius': false,
|
| - 'fps': true
|
| -};
|
| -
|
| -var glowEffectOn = true;
|
| -const GLOWSHADOWBLUR = 8;
|
| -const SCOREDBKEY = "asteroids-score-1.1";
|
| -
|
| -var _asteroidImgs = [];
|
| -var _shieldImg = new ImageElement();
|
| -var _backgroundImg = new ImageElement();
|
| -var _playerImg = new ImageElement();
|
| -var _enemyshipImg = new ImageElement();
|
| -var soundManager;
|
| -
|
| -/** Asteroids color constants */
|
| -class Colors {
|
| - static const PARTICLE = "rgb(255,125,50)";
|
| - static const ENEMY_SHIP = "rgb(200,200,250)";
|
| - static const ENEMY_SHIP_DARK = "rgb(150,150,200)";
|
| - static const GREEN_LASER = "rgb(120,255,120)";
|
| - static const GREEN_LASER_DARK = "rgb(50,255,50)";
|
| - static const GREEN_LASERX2 = "rgb(120,255,150)";
|
| - static const GREEN_LASERX2_DARK = "rgb(50,255,75)";
|
| - static const PLAYER_BOMB = "rgb(155,255,155)";
|
| - static const PLAYER_THRUST = "rgb(25,125,255)";
|
| - static const PLAYER_SHIELD = "rgb(100,100,255)";
|
| -}
|
| -
|
| -/**
|
| - * Actor base class.
|
| - *
|
| - * Game actors have a position in the game world and a current vector to
|
| - * indicate direction and speed of travel per frame. They each support the
|
| - * onUpdate() and onRender() event methods, finally an actor has an expired()
|
| - * method which should return true when the actor object should be removed
|
| - * from play.
|
| - */
|
| -class Actor {
|
| - Vector position, velocity;
|
| -
|
| - Actor(this.position, this.velocity);
|
| -
|
| - /**
|
| - * Actor game loop update event method. Called for each actor
|
| - * at the start of each game loop cycle.
|
| - */
|
| - onUpdate(Scene scene) {}
|
| -
|
| - /**
|
| - * Actor rendering event method. Called for each actor to
|
| - * render for each frame.
|
| - */
|
| - void onRender(CanvasRenderingContext2D ctx) {}
|
| -
|
| - /**
|
| - * Actor expiration test; return true if expired and to be removed
|
| - * from the actor list, false if still in play.
|
| - */
|
| - bool expired() => false;
|
| -
|
| - get frameMultiplier => GameHandler.frameMultiplier;
|
| - get frameStart => GameHandler.frameStart;
|
| - get canvas_height => GameHandler.height;
|
| - get canvas_width => GameHandler.width;
|
| -}
|
| -
|
| -// Short-lived actors (like particles and munitions). These have a
|
| -// start time and lifespan, and fade out after a period.
|
| -
|
| -class ShortLivedActor extends Actor {
|
| - int lifespan;
|
| - int start;
|
| -
|
| - ShortLivedActor(Vector position, Vector velocity,
|
| - this.lifespan)
|
| - : super(position, velocity),
|
| - this.start = GameHandler.frameStart;
|
| -
|
| - bool expired() => (frameStart - start > lifespan);
|
| -
|
| - /**
|
| - * Helper to return a value multiplied by the ratio of the remaining lifespan
|
| - */
|
| - double fadeValue(double val, int offset) {
|
| - var rem = lifespan - (frameStart - start),
|
| - result = val;
|
| - if (rem < offset) {
|
| - result = (val / offset) * rem;
|
| - result = Math.max(0.0, Math.min(result, val));
|
| - }
|
| - return result;
|
| - }
|
| -}
|
| -
|
| -class AttractorScene extends Scene {
|
| - AsteroidsMain game;
|
| -
|
| - AttractorScene(this.game)
|
| - : super(false, null) {
|
| - }
|
| -
|
| - bool start = false;
|
| - bool imagesLoaded = false;
|
| - double sine = 0.0;
|
| - double mult = 0.0;
|
| - double multIncrement = 0.0;
|
| - List actors = null;
|
| - const SCENE_LENGTH = 400;
|
| - const SCENE_FADE = 75;
|
| - List sceneRenderers = null;
|
| - int currentSceneRenderer = 0;
|
| - int currentSceneFrame = 0;
|
| -
|
| - bool isComplete() => start;
|
| -
|
| - void onInitScene() {
|
| - start = false;
|
| - mult = 512.0;
|
| - multIncrement = 0.5;
|
| - currentSceneRenderer = 0;
|
| - currentSceneFrame = 0;
|
| -
|
| - // scene renderers
|
| - // display welcome text, info text and high scores
|
| - sceneRenderers = [
|
| - sceneRendererWelcome,
|
| - sceneRendererInfo,
|
| - sceneRendererScores ];
|
| -
|
| - // randomly generate some background asteroids for attractor scene
|
| - actors = [];
|
| - for (var i = 0; i < 8; i++) {
|
| - var pos = new Vector(random() * GameHandler.width.toDouble(),
|
| - random() * GameHandler.height.toDouble());
|
| - var vec = new Vector(((random() * 2.0) - 1.0), ((random() * 2.0) - 1.0));
|
| - actors.add(new Asteroid(pos, vec, randomInt(3, 4)));
|
| - }
|
| -
|
| - game.score = 0;
|
| - game.lives = 3;
|
| - }
|
| -
|
| - void onRenderScene(CanvasRenderingContext2D ctx) {
|
| - if (imagesLoaded) {
|
| - // Draw the background asteroids.
|
| - for (var i = 0; i < actors.length; i++) {
|
| - var actor = actors[i];
|
| - actor.onUpdate(this);
|
| - game.updateActorPosition(actor);
|
| - actor.onRender(ctx);
|
| - }
|
| -
|
| - // Handle cycling through scenes.
|
| - if (++currentSceneFrame == SCENE_LENGTH) { // Move to next scene.
|
| - if (++currentSceneRenderer == sceneRenderers.length) {
|
| - currentSceneRenderer = 0; // Wrap to first scene.
|
| - }
|
| - currentSceneFrame = 0;
|
| - }
|
| -
|
| - ctx.save();
|
| -
|
| - // fade in/out
|
| - if (currentSceneFrame < SCENE_FADE) {
|
| - // fading in
|
| - ctx.globalAlpha = 1 - ((SCENE_FADE - currentSceneFrame) / SCENE_FADE);
|
| - } else if (currentSceneFrame >= SCENE_LENGTH - SCENE_FADE) {
|
| - // fading out
|
| - ctx.globalAlpha = ((SCENE_LENGTH - currentSceneFrame) / SCENE_FADE);
|
| - } else {
|
| - ctx.globalAlpha = 1.0;
|
| - }
|
| -
|
| - sceneRenderers[currentSceneRenderer](ctx);
|
| -
|
| - ctx.restore();
|
| -
|
| - sineText(ctx, "BLASTEROIDS",
|
| - GameHandler.width ~/ 2 - 130, GameHandler.height ~/ 2 - 64);
|
| - } else {
|
| - centerFillText(ctx, "Loading...",
|
| - "18pt Courier New", GameHandler.height ~/ 2, "white");
|
| - }
|
| - }
|
| -
|
| - void sceneRendererWelcome(CanvasRenderingContext2D ctx) {
|
| - ctx.fillStyle = ctx.strokeStyle = "white";
|
| - centerFillText(ctx, "Press SPACE or click to start", "18pt Courier New",
|
| - GameHandler.height ~/ 2);
|
| - fillText(ctx, "based on Javascript game by Kevin Roast",
|
| - "10pt Courier New", 16, 624);
|
| - }
|
| -
|
| - void sceneRendererInfo(CanvasRenderingContext2D ctx) {
|
| - ctx.fillStyle = ctx.strokeStyle = "white";
|
| - fillText(ctx, "How to play...", "14pt Courier New", 40, 320);
|
| - fillText(ctx, "Arrow keys or tilt to rotate, thrust, shield. "
|
| - "SPACE or touch to fire.",
|
| - "14pt Courier New", 40, 350);
|
| - fillText(ctx, "Pickup the glowing power-ups to enhance your ship.",
|
| - "14pt Courier New", 40, 370);
|
| - fillText(ctx, "Watch out for enemy saucers!", "14pt Courier New", 40, 390);
|
| - }
|
| -
|
| - void sceneRendererScores(CanvasRenderingContext2D ctx) {
|
| - ctx.fillStyle = ctx.strokeStyle = "white";
|
| - centerFillText(ctx, "High Score", "18pt Courier New", 320);
|
| - var sscore = this.game.highscore.toString();
|
| - // pad with zeros
|
| - for (var i=0, j=8-sscore.length; i<j; i++) {
|
| - sscore = "0$sscore";
|
| - }
|
| - centerFillText(ctx, sscore, "18pt Courier New", 350);
|
| - }
|
| -
|
| - /** Callback from image preloader when all images are ready */
|
| - void ready() {
|
| - imagesLoaded = true;
|
| - }
|
| -
|
| - /**
|
| - * Render the a text string in a pulsing x-sine y-cos wave pattern
|
| - * The multiplier for the sinewave is modulated over time
|
| - */
|
| - void sineText(CanvasRenderingContext2D ctx, String txt, int xpos, int ypos) {
|
| - mult += multIncrement;
|
| - if (mult > 1024.0) {
|
| - multIncrement = -multIncrement;
|
| - } else if (this.mult < 128.0) {
|
| - multIncrement = -multIncrement;
|
| - }
|
| - var offset = sine;
|
| - for (var i = 0; i < txt.length; i++) {
|
| - var y = ypos + ((Math.sin(offset) * RAD) * mult).toInt();
|
| - var x = xpos + ((Math.cos(offset++) * RAD) * (mult * 0.5)).toInt();
|
| - fillText(ctx, txt[i], "36pt Courier New", x + i * 30, y, "white");
|
| - }
|
| - sine += 0.075;
|
| - }
|
| -
|
| - bool onKeyDownHandler(int keyCode) {
|
| - log("In onKeyDownHandler, AttractorScene");
|
| - switch (keyCode) {
|
| - case Key.SPACE:
|
| - if (imagesLoaded) {
|
| - start = true;
|
| - }
|
| - return true;
|
| - case Key.ESC:
|
| - GameHandler.togglePause();
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - bool onMouseDownHandler(e) {
|
| - if (imagesLoaded) {
|
| - start = true;
|
| - }
|
| - return true;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * An actor representing a transient effect in the game world. An effect is
|
| - * nothing more than a special graphic that does not play any direct part in
|
| - * the game and does not interact with any other objects. It automatically
|
| - * expires after a set lifespan, generally the rendering of the effect is
|
| - * based on the remaining lifespan.
|
| - */
|
| -class EffectActor extends Actor {
|
| - int lifespan; // in msec.
|
| - int effectStart; // start time
|
| -
|
| - EffectActor(Vector position , Vector velocity, [this.lifespan = 0])
|
| - : super(position, velocity) {
|
| - effectStart = frameStart;
|
| - }
|
| -
|
| - bool expired() => (frameStart - effectStart > lifespan);
|
| -
|
| - /**
|
| - * Helper for an effect to return the value multiplied by the ratio of the
|
| - * remaining lifespan of the effect.
|
| - */
|
| - double effectValue(double val) {
|
| - var result = val - (val * (frameStart - effectStart)) / lifespan;
|
| - return Math.max(0.0, Math.min(val, result));
|
| - }
|
| -}
|
| -
|
| -/** Text indicator effect actor class. */
|
| -class TextIndicator extends EffectActor {
|
| - int fadeLength;
|
| - int textSize;
|
| - String msg;
|
| - String color;
|
| -
|
| - TextIndicator(Vector position, Vector velocity, this.msg,
|
| - [this.textSize = 12, this.color = "white",
|
| - int fl = 500]) :
|
| - super(position, velocity, fl), fadeLength = fl;
|
| -
|
| - const DEFAULT_FADE_LENGTH = 500;
|
| -
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - // Fade out alpha.
|
| - ctx.save();
|
| - ctx.globalAlpha = effectValue(1.0);
|
| - fillText(ctx, msg, "${textSize}pt Courier New",
|
| - position.x, position.y, color);
|
| - ctx.restore();
|
| - }
|
| -}
|
| -
|
| -/** Score indicator effect actor class. */
|
| -class ScoreIndicator extends TextIndicator {
|
| - ScoreIndicator(Vector position, Vector velocity, int score,
|
| - [int textSize = 12, String prefix = '', String color = "white",
|
| - int fadeLength = 500]) :
|
| - super(position, velocity, '${prefix.length > 0 ? "$prefix " : ""}${score}',
|
| - textSize, color, fadeLength);
|
| -}
|
| -
|
| -/** Power up collectable. */
|
| -class PowerUp extends EffectActor {
|
| - PowerUp(Vector position, Vector velocity)
|
| - : super(position, velocity);
|
| -
|
| - const RADIUS = 8;
|
| - int pulse = 128;
|
| - int pulseinc = 5;
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.globalAlpha = 0.75;
|
| - var col = "rgb(255,${pulse.toString()},0)";
|
| - ctx.fillStyle = col;
|
| - ctx.strokeStyle = "rgb(255,255,128)";
|
| - ctx.beginPath();
|
| - ctx.arc(position.x, position.y, RADIUS, 0, TWOPI, true);
|
| - ctx.closePath();
|
| - ctx.fill();
|
| - ctx.stroke();
|
| - ctx.restore();
|
| - pulse += pulseinc;
|
| - if (pulse > 255){
|
| - pulse = 256 - pulseinc;
|
| - pulseinc =- pulseinc;
|
| - } else if (pulse < 0) {
|
| - pulse = 0 - pulseinc;
|
| - pulseinc =- pulseinc;
|
| - }
|
| - }
|
| -
|
| - get radius => RADIUS;
|
| -
|
| - void collected(AsteroidsMain game, Player player, GameScene scene) {
|
| - // Randomly select a powerup to apply.
|
| - var message = null;
|
| - var n, m, enemy, pos;
|
| - switch (randomInt(0, 9)) {
|
| - case 0:
|
| - case 1:
|
| - message = "Energy Boost!";
|
| - player.energy += player.ENERGY_INIT / 2;
|
| - if (player.energy > player.ENERGY_INIT) {
|
| - player.energy = player.ENERGY_INIT;
|
| - }
|
| - break;
|
| -
|
| - case 2:
|
| - message = "Fire When Shielded!";
|
| - player.fireWhenShield = true;
|
| - break;
|
| -
|
| - case 3:
|
| - message = "Extra Life!";
|
| - game.lives++;
|
| - break;
|
| -
|
| - case 4:
|
| - message = "Slow Down Asteroids!";
|
| - m = scene.enemies.length;
|
| - for (n = 0; n < m; n++) {
|
| - enemy = scene.enemies[n];
|
| - if (enemy is Asteroid) {
|
| - enemy.velocity.scale(0.66);
|
| - }
|
| - }
|
| - break;
|
| -
|
| - case 5:
|
| - message = "Smart Bomb!";
|
| -
|
| - var effectRad = 96;
|
| -
|
| - // Add a BIG explosion actor at the smart bomb weapon position
|
| - // and vector.
|
| - var boom = new Explosion(position.clone(),
|
| - velocity.nscale(0.5), effectRad / 8);
|
| - scene.effects.add(boom);
|
| -
|
| - // Test circle intersection with each enemy actor.
|
| - // We check the enemy list length each iteration to catch baby asteroids
|
| - // this is a fully fledged smart bomb after all!
|
| - pos = position;
|
| - for (n = 0; n < scene.enemies.length; n++) {
|
| - enemy = scene.enemies[n];
|
| -
|
| - // Test the distance against the two radius combined.
|
| - if (pos.distance(enemy.position) <= effectRad + enemy.radius) {
|
| - // Intersection detected!
|
| - enemy.hit(-1);
|
| - scene.generatePowerUp(enemy);
|
| - scene.destroyEnemy(enemy, velocity, true);
|
| - }
|
| - }
|
| - break;
|
| -
|
| - case 6:
|
| - message = "Twin Cannons!";
|
| - player.primaryWeapons["main"] = new TwinCannonsWeapon(player);
|
| - break;
|
| -
|
| - case 7:
|
| - message = "Spray Cannons!";
|
| - player.primaryWeapons["main"] = new VSprayCannonsWeapon(player);
|
| - break;
|
| -
|
| - case 8:
|
| - message = "Rear Gun!";
|
| - player.primaryWeapons["rear"] = new RearGunWeapon(player);
|
| - break;
|
| -
|
| - case 9:
|
| - message = "Side Guns!";
|
| - player.primaryWeapons["side"] = new SideGunWeapon(player);
|
| - break;
|
| - }
|
| -
|
| - if (message != null) {
|
| - // Generate a effect indicator at the destroyed enemy position.
|
| - var vec = new Vector(0.0, -1.5);
|
| - var effect = new TextIndicator(
|
| - new Vector(position.x, position.y - RADIUS), vec,
|
| - message, null, null, 700);
|
| - scene.effects.add(effect);
|
| - }
|
| - }
|
| -}
|
| -/**
|
| - * This is the common base class of actors that can be hit and destroyed by
|
| - * player bullets. It supports a hit() method which should return true when
|
| - * the enemy object should be removed from play.
|
| - */
|
| -class EnemyActor extends SpriteActor {
|
| - EnemyActor(Vector position, Vector velocity, this.size)
|
| - : super(position, velocity);
|
| -
|
| - bool alive = true;
|
| -
|
| - /** Size - values from 1-4 are valid for asteroids, 0-1 for ships. */
|
| - int size;
|
| -
|
| - bool expired() => !alive;
|
| -
|
| - bool hit(num force) {
|
| - alive = false;
|
| - return true;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Asteroid actor class.
|
| - */
|
| -class Asteroid extends EnemyActor {
|
| - Asteroid(Vector position, Vector velocity, int size, [this.type])
|
| - : super(position, velocity, size) {
|
| - health = size;
|
| -
|
| - // Randomly select an asteroid image bitmap.
|
| - if (type == null) {
|
| - type = randomInt(1, 4);
|
| - }
|
| - animImage = _asteroidImgs[type-1];
|
| -
|
| - // Rrandomly setup animation speed and direction.
|
| - animForward = (random() < 0.5);
|
| - animSpeed = 0.3 + random() * 0.5;
|
| - animLength = ANIMATION_LENGTH;
|
| - rotation = randomInt(0, 180);
|
| - rotationSpeed = (random() - 0.5) / 30;
|
| - }
|
| -
|
| - const ANIMATION_LENGTH = 180;
|
| -
|
| - /** Asteroid graphic type i.e. which bitmap it is drawn from. */
|
| - int type;
|
| -
|
| - /** Asteroid health before it's destroyed. */
|
| - num health = 0;
|
| -
|
| - /** Retro graphics mode rotation orientation and speed. */
|
| - int rotation = 0;
|
| - double rotationSpeed = 0.0;
|
| -
|
| - /** Asteroid rendering method. */
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - var rad = size * 8;
|
| - ctx.save();
|
| - // Render asteroid graphic bitmap. The bitmap is rendered slightly large
|
| - // than the radius as the raytraced asteroid graphics do not quite touch
|
| - // the edges of the 64x64 sprite - this improves perceived collision
|
| - // detection.
|
| - renderSprite(ctx, position.x - rad - 2, position.y - rad - 2, (rad * 2)+4);
|
| - ctx.restore();
|
| - }
|
| -
|
| - get radius => size * 8;
|
| -
|
| - bool hit(num force) {
|
| - if (force != -1) {
|
| - health -= force;
|
| - } else {
|
| - // instant kill
|
| - health = 0;
|
| - }
|
| - return !(alive = (health > 0));
|
| - }
|
| -}
|
| -
|
| -/** Enemy Ship actor class. */
|
| -class EnemyShip extends EnemyActor {
|
| -
|
| - get radius => _radius;
|
| -
|
| - EnemyShip(GameScene scene, int size)
|
| - : super(null, null, size) {
|
| - // Small ship, alter settings slightly.
|
| - if (size == 1) {
|
| - BULLET_RECHARGE_MS = 1300;
|
| - _radius = 8;
|
| - } else {
|
| - _radius = 16;
|
| - }
|
| -
|
| - // Randomly setup enemy initial position and vector
|
| - // ensure the enemy starts in the opposite quadrant to the player.
|
| - var p, v;
|
| - if (scene.player.position.x < canvas_width / 2) {
|
| - // Player on left of the screen.
|
| - if (scene.player.position.y < canvas_height / 2) {
|
| - // Player in top left of the screen.
|
| - position = new Vector(canvas_width-48, canvas_height-48);
|
| - } else {
|
| - // Player in bottom left of the screen.
|
| - position = new Vector(canvas_width-48, 48);
|
| - }
|
| - velocity = new Vector(-(random() + 0.25 + size * 0.75),
|
| - random() + 0.25 + size * 0.75);
|
| - } else {
|
| - // Player on right of the screen.
|
| - if (scene.player.position.y < canvas_height / 2) {
|
| - // Player in top right of the screen.
|
| - position = new Vector(0, canvas_height-48);
|
| - } else {
|
| - // Player in bottom right of the screen.
|
| - position = new Vector(0, 48);
|
| - }
|
| - velocity = new Vector(random() + 0.25 + size * 0.75,
|
| - random() + 0.25 + size * 0.75);
|
| - }
|
| -
|
| - // Setup SpriteActor values.
|
| - animImage = _enemyshipImg;
|
| - animLength = SHIP_ANIM_LENGTH;
|
| - }
|
| -
|
| - const SHIP_ANIM_LENGTH = 90;
|
| - int _radius;
|
| - int BULLET_RECHARGE_MS = 1800;
|
| -
|
| -
|
| - /** True if ship alive, false if ready for expiration. */
|
| - bool alive = true;
|
| -
|
| - /** Bullet fire recharging counter. */
|
| - int bulletRecharge = 0;
|
| -
|
| - void onUpdate(GameScene scene) {
|
| - // change enemy direction randomly
|
| - if (size == 0) {
|
| - if (random() < 0.01) {
|
| - velocity.y = -(velocity.y + (0.25 - (random()/2)));
|
| - }
|
| - } else {
|
| - if (random() < 0.02) {
|
| - velocity.y = -(velocity.y + (0.5 - random()));
|
| - }
|
| - }
|
| -
|
| - // regular fire a bullet at the player
|
| - if (frameStart - bulletRecharge >
|
| - BULLET_RECHARGE_MS && scene.player.alive) {
|
| - // ok, update last fired time and we can now generate a bullet
|
| - bulletRecharge = frameStart;
|
| -
|
| - // generate a vector pointed at the player
|
| - // by calculating a vector between the player and enemy positions
|
| - var v = scene.player.position.clone().sub(position);
|
| - // scale resulting vector down to bullet vector size
|
| - var scale = (size == 0 ? 3.0 : 3.5) / v.length();
|
| - v.x *= scale;
|
| - v.y *= scale;
|
| - // slightly randomize the direction (big ship is less accurate also)
|
| - v.x += (size == 0 ? (random() * 2.0 - 1.0) : (random() - 0.5));
|
| - v.y += (size == 0 ? (random() * 2.0 - 1.0) : (random() - 0.5));
|
| - // - could add the enemy motion vector for correct momentum
|
| - // - but this leads to slow bullets firing back from dir of travel
|
| - // - so pretend that enemies are clever enough to account for this...
|
| - //v.add(this.vector);
|
| -
|
| - var bullet = new EnemyBullet(position.clone(), v);
|
| - scene.enemyBullets.add(bullet);
|
| - //soundManager.play('enemy_bomb');
|
| - }
|
| - }
|
| -
|
| - /** Enemy rendering method. */
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - // render enemy graphic bitmap
|
| - var rad = radius + 2;
|
| - renderSprite(ctx, position.x - rad, position.y - rad, rad * 2);
|
| - }
|
| -
|
| - /** Enemy hit by a bullet; return true if destroyed, false otherwise. */
|
| - bool hit(num force) {
|
| - alive = false;
|
| - return true;
|
| - }
|
| -
|
| - bool expired() {
|
| - return !alive;
|
| - }
|
| -}
|
| -
|
| -class GameCompleted extends Scene {
|
| - AsteroidsMain game;
|
| - var player;
|
| -
|
| - GameCompleted(this.game)
|
| - : super(false) {
|
| - interval = new Interval("CONGRATULATIONS!", intervalRenderer);
|
| - player = game.player;
|
| - }
|
| -
|
| - bool isComplete() => true;
|
| -
|
| - void intervalRenderer(Interval interval, CanvasRenderingContext2D ctx) {
|
| - if (interval.framecounter++ == 0) {
|
| - if (game.score == game.highscore) {
|
| - // save new high score to HTML5 local storage
|
| - if (window.localStorage) {
|
| - window.localStorage[SCOREDBKEY] = game.score;
|
| - }
|
| - }
|
| - }
|
| - if (interval.framecounter < 1000) {
|
| - fillText(ctx, interval.label, "18pt Courier New",
|
| - GameHandler.width ~/ 2 - 96, GameHandler.height ~/ 2 - 32, "white");
|
| - fillText(ctx, "Score: ${game.score}", "14pt Courier New",
|
| - GameHandler.width ~/ 2 - 64, GameHandler.height ~/ 2, "white");
|
| - if (game.score == game.highscore) {
|
| - fillText(ctx, "New High Score!", "14pt Courier New",
|
| - GameHandler.width ~/ 2 - 64,
|
| - GameHandler.height ~/ 2 + 24, "white");
|
| - }
|
| - } else {
|
| - interval.complete = true;
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Game Handler.
|
| - *
|
| - * Singleton instance responsible for managing the main game loop and
|
| - * maintaining a few global references such as the canvas and frame counters.
|
| - */
|
| -class GameHandler {
|
| - /**
|
| - * The single Game.Main derived instance
|
| - */
|
| - static GameMain game = null;
|
| -
|
| - static bool paused = false;
|
| - static CanvasElement canvas = null;
|
| - static int width = 0;
|
| - static int height = 0;
|
| - static int frameCount = 0;
|
| -
|
| - /** Frame multiplier - i.e. against the ideal fps. */
|
| - static double frameMultiplier = 1.0;
|
| -
|
| - /** Last frame start time in ms. */
|
| - static int frameStart = 0;
|
| -
|
| - /** Debugging output. */
|
| - static int maxfps = 0;
|
| -
|
| - /** Ideal FPS constant. */
|
| - static const FPSMS = 1000 / 60;
|
| -
|
| - static Prerenderer bitmaps;
|
| -
|
| - /** Init function called once by your window.onload handler. */
|
| - static void init(c) {
|
| - canvas = c;
|
| - width = canvas.width;
|
| - height = canvas.height;
|
| - log("Init GameMain($c,$width,$height)");
|
| - }
|
| -
|
| - /**
|
| - * Game start method - begins the main game loop.
|
| - * Pass in the object that represent the game to execute.
|
| - */
|
| - static void start(GameMain g) {
|
| - game = g;
|
| - frameStart = new DateTime.now().millisecondsSinceEpoch;
|
| - log("Doing first frame");
|
| - game.frame();
|
| - }
|
| -
|
| - /** Called each frame by the main game loop unless paused. */
|
| - static void doFrame(_) {
|
| - log("Doing next frame");
|
| - game.frame();
|
| - }
|
| -
|
| - static void togglePause() {
|
| - if (paused) {
|
| - paused = false;
|
| - frameStart = new DateTime.now().millisecondsSinceEpoch;
|
| - game.frame();
|
| - } else {
|
| - paused = true;
|
| - }
|
| - }
|
| -
|
| - static bool onAccelerometer(double x, double y, double z) {
|
| - return game == null ? true : game.onAccelerometer(x, y, z);
|
| - }
|
| -}
|
| -
|
| -bool onAccelerometer(double x, double y, double z) {
|
| - return GameHandler.onAccelerometer(x, y, z);
|
| -}
|
| -
|
| -/** Game main loop class. */
|
| -class GameMain {
|
| -
|
| - GameMain() {
|
| - var me = this;
|
| -
|
| - document.onKeyDown.listen((KeyboardEvent event) {
|
| - var keyCode = event.keyCode;
|
| -
|
| - log("In document.onKeyDown($keyCode)");
|
| - if (me.sceneIndex != -1) {
|
| - if (me.scenes[me.sceneIndex].onKeyDownHandler(keyCode) != null) {
|
| - // if the key is handled, prevent any further events
|
| - if (event != null) {
|
| - event.preventDefault();
|
| - event.stopPropagation();
|
| - }
|
| - }
|
| - }
|
| - });
|
| -
|
| - document.onKeyUp.listen((KeyboardEvent event) {
|
| - var keyCode = event.keyCode;
|
| - if (me.sceneIndex != -1) {
|
| - if (me.scenes[me.sceneIndex].onKeyUpHandler(keyCode) != null) {
|
| - // if the key is handled, prevent any further events
|
| - if (event != null) {
|
| - event.preventDefault();
|
| - event.stopPropagation();
|
| - }
|
| - }
|
| - }
|
| - });
|
| -
|
| - document.onMouseDown.listen((MouseEvent event) {
|
| - if (me.sceneIndex != -1) {
|
| - if (me.scenes[me.sceneIndex].onMouseDownHandler(event) != null) {
|
| - // if the event is handled, prevent any further events
|
| - if (event != null) {
|
| - event.preventDefault();
|
| - event.stopPropagation();
|
| - }
|
| - }
|
| - }
|
| - });
|
| -
|
| - document.onMouseUp.listen((MouseEvent event) {
|
| - if (me.sceneIndex != -1) {
|
| - if (me.scenes[me.sceneIndex].onMouseUpHandler(event) != null) {
|
| - // if the event is handled, prevent any further events
|
| - if (event != null) {
|
| - event.preventDefault();
|
| - event.stopPropagation();
|
| - }
|
| - }
|
| - }
|
| - });
|
| -
|
| - }
|
| -
|
| - List scenes = [];
|
| - Scene startScene = null;
|
| - Scene endScene = null;
|
| - Scene currentScene = null;
|
| - int sceneIndex = -1;
|
| - var interval = null;
|
| - int totalFrames = 0;
|
| -
|
| - bool onAccelerometer(double x, double y, double z) {
|
| - if (currentScene != null) {
|
| - return currentScene.onAccelerometer(x, y, z);
|
| - }
|
| - return true;
|
| - }
|
| - /**
|
| - * Game frame execute method - called by anim handler timeout
|
| - */
|
| - void frame() {
|
| - var frameStart = new DateTime.now().millisecondsSinceEpoch;
|
| -
|
| - // Calculate scene transition and current scene.
|
| - if (currentScene == null) {
|
| - // Set to scene zero (game init).
|
| - currentScene = scenes[sceneIndex = 0];
|
| - currentScene.onInitScene();
|
| - } else if (isGameOver()) {
|
| - sceneIndex = -1;
|
| - currentScene = endScene;
|
| - currentScene.onInitScene();
|
| - }
|
| -
|
| - if ((currentScene.interval == null ||
|
| - currentScene.interval.complete) && currentScene.isComplete()) {
|
| - if (++sceneIndex >= scenes.length){
|
| - sceneIndex = 0;
|
| - }
|
| - currentScene = scenes[sceneIndex];
|
| - currentScene.onInitScene();
|
| - }
|
| -
|
| - var ctx = GameHandler.canvas.getContext('2d');
|
| -
|
| - // Rrender the game and current scene.
|
| - ctx.save();
|
| - if (currentScene.interval == null || currentScene.interval.complete) {
|
| - currentScene.onBeforeRenderScene();
|
| - onRenderGame(ctx);
|
| - currentScene.onRenderScene(ctx);
|
| - } else {
|
| - onRenderGame(ctx);
|
| - currentScene.interval.intervalRenderer(currentScene.interval, ctx);
|
| - }
|
| - ctx.restore();
|
| -
|
| - GameHandler.frameCount++;
|
| -
|
| - // Calculate frame total time interval and frame multiplier required
|
| - // for smooth animation.
|
| -
|
| - // Time since last frame.
|
| - var frameInterval = frameStart - GameHandler.frameStart;
|
| - if (frameInterval == 0) frameInterval = 1;
|
| - if (GameHandler.frameCount % 16 == 0) { // Update fps every 16 frames
|
| - GameHandler.maxfps = (1000 / frameInterval).floor().toInt();
|
| - }
|
| - GameHandler.frameMultiplier = frameInterval.toDouble() / GameHandler.FPSMS;
|
| -
|
| - GameHandler.frameStart = frameStart;
|
| -
|
| - if (!GameHandler.paused) {
|
| - window.requestAnimationFrame(GameHandler.doFrame);
|
| - }
|
| - if ((++totalFrames % 600) == 0) {
|
| - log('${totalFrames} frames; multiplier ${GameHandler.frameMultiplier}');
|
| - }
|
| - }
|
| -
|
| - void onRenderGame(CanvasRenderingContext2D ctx) {}
|
| -
|
| - bool isGameOver() => false;
|
| -}
|
| -
|
| -class AsteroidsMain extends GameMain {
|
| -
|
| - AsteroidsMain() : super() {
|
| - var attractorScene = new AttractorScene(this);
|
| -
|
| - // get the images graphics loading
|
| - var loader = new Preloader();
|
| - loader.addImage(_playerImg, 'player.png');
|
| - loader.addImage(_asteroidImgs[0], 'asteroid1.png');
|
| - loader.addImage(_asteroidImgs[1], 'asteroid2.png');
|
| - loader.addImage(_asteroidImgs[2], 'asteroid3.png');
|
| - loader.addImage(_asteroidImgs[3], 'asteroid4.png');
|
| - loader.addImage(_shieldImg, 'shield.png');
|
| - loader.addImage(_enemyshipImg, 'enemyship1.png');
|
| -
|
| - // The attactor scene is displayed first and responsible for allowing the
|
| - // player to start the game once all images have been loaded.
|
| - loader.onLoadCallback(() {
|
| - attractorScene.ready();
|
| - });
|
| -
|
| - // Generate the single player actor - available across all scenes.
|
| - player = new Player(
|
| - new Vector(GameHandler.width / 2, GameHandler.height / 2),
|
| - new Vector(0.0, 0.0),
|
| - 0.0);
|
| -
|
| - scenes.add(attractorScene);
|
| -
|
| - for (var i = 0; i < 12; i++){
|
| - var level = new GameScene(this, i+1);
|
| - scenes.add(level);
|
| - }
|
| -
|
| - scenes.add(new GameCompleted(this));
|
| -
|
| - // Set special end scene member value to a Game Over scene.
|
| - endScene = new GameOverScene(this);
|
| -
|
| - if (window.localStorage.containsKey(SCOREDBKEY)) {
|
| - highscore = int.parse(window.localStorage[SCOREDBKEY]);
|
| - }
|
| - // Perform prerender steps - create some bitmap graphics to use later.
|
| - GameHandler.bitmaps = new Prerenderer();
|
| - GameHandler.bitmaps.execute();
|
| - }
|
| -
|
| - Player player = null;
|
| - int lives = 0;
|
| - int score = 0;
|
| - int highscore = 0;
|
| - /** Background scrolling bitmap x position */
|
| - double backgroundX = 0.0;
|
| - /** Background starfield star list */
|
| - List starfield = [];
|
| -
|
| - void onRenderGame(CanvasRenderingContext2D ctx) {
|
| - // Setup canvas for a render pass and apply background
|
| - // draw a scrolling background image.
|
| - var w = GameHandler.width;
|
| - var h = GameHandler.height;
|
| - //var sourceRect = new Rect(backgroundX, 0, w, h);
|
| - //var destRect = new Rect(0, 0, w, h);
|
| - //ctx.drawImageToRect(_backgroundImg, destRect,
|
| - // sourceRect:sourceRect);
|
| - ctx.drawImageScaledFromSource(_backgroundImg,
|
| - backgroundX, 0, w, h, 0, 0, w, h);
|
| -
|
| - backgroundX += (GameHandler.frameMultiplier / 4.0);
|
| - if (backgroundX >= _backgroundImg.width / 2) {
|
| - backgroundX -= _backgroundImg.width / 2;
|
| - }
|
| - ctx.shadowBlur = 0;
|
| - }
|
| -
|
| - bool isGameOver() {
|
| - if (currentScene is GameScene) {
|
| - var gs = currentScene as GameScene;
|
| - return (lives == 0 && gs.effects != null && gs.effects.length == 0);
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - /**
|
| - * Update an actor position using its current velocity vector.
|
| - * Scale the vector by the frame multiplier - this is used to ensure
|
| - * all actors move the same distance over time regardles of framerate.
|
| - * Also handle traversing out of the coordinate space and back again.
|
| - */
|
| - void updateActorPosition(Actor actor) {
|
| - actor.position.add(actor.velocity.nscale(GameHandler.frameMultiplier));
|
| - actor.position.wrap(0, GameHandler.width - 1, 0, GameHandler.height - 1);
|
| - }
|
| -}
|
| -
|
| -class GameOverScene extends Scene {
|
| - var game, player;
|
| -
|
| - GameOverScene(this.game) :
|
| - super(false) {
|
| - interval = new Interval("GAME OVER", intervalRenderer);
|
| - player = game.player;
|
| - }
|
| -
|
| - bool isComplete() => true;
|
| -
|
| - void intervalRenderer(Interval interval, CanvasRenderingContext2D ctx) {
|
| - if (interval.framecounter++ == 0) {
|
| - if (game.score == game.highscore) {
|
| - window.localStorage[SCOREDBKEY] = game.score.toString();
|
| - }
|
| - }
|
| - if (interval.framecounter < 300) {
|
| - fillText(ctx, interval.label, "18pt Courier New",
|
| - GameHandler.width * 0.5 - 64, GameHandler.height*0.5 - 32, "white");
|
| - fillText(ctx, "Score: ${game.score}", "14pt Courier New",
|
| - GameHandler.width * 0.5 - 64, GameHandler.height*0.5, "white");
|
| - if (game.score == game.highscore) {
|
| - fillText(ctx, "New High Score!", "14pt Courier New",
|
| - GameHandler.width * 0.5 - 64, GameHandler.height*0.5 + 24, "white");
|
| - }
|
| - } else {
|
| - interval.complete = true;
|
| - }
|
| - }
|
| -}
|
| -
|
| -class GameScene extends Scene {
|
| - AsteroidsMain game;
|
| - int wave;
|
| - var player;
|
| - List actors = null;
|
| - List playerBullets = null;
|
| - List enemies = null;
|
| - List enemyBullets = null;
|
| - List effects = null;
|
| - List collectables = null;
|
| - int enemyShipCount = 0;
|
| - int enemyShipAdded = 0;
|
| - int scoredisplay = 0;
|
| - bool skipLevel = false;
|
| -
|
| - Input input;
|
| -
|
| - GameScene(this.game, this.wave)
|
| - : super(true) {
|
| - interval = new Interval("Wave ${wave}", intervalRenderer);
|
| - player = game.player;
|
| - input = new Input();
|
| - }
|
| -
|
| - void onInitScene() {
|
| - // Generate the actors and add the actor sub-lists to the main actor list.
|
| - actors = [];
|
| - enemies = [];
|
| - actors.add(enemies);
|
| - actors.add(playerBullets = []);
|
| - actors.add(enemyBullets = []);
|
| - actors.add(effects = []);
|
| - actors.add(collectables = []);
|
| -
|
| - // Reset player ready for game restart.
|
| - resetPlayerActor(wave != 1);
|
| -
|
| - // Randomly generate some asteroids.
|
| - var factor = 1.0 + ((wave - 1) * 0.075);
|
| - for (var i=1, j=(4 + wave); i < j; i++) {
|
| - enemies.add(generateAsteroid(factor));
|
| - }
|
| -
|
| - // Reset enemy ship count and last enemy added time.
|
| - enemyShipAdded = GameHandler.frameStart;
|
| - enemyShipCount = 0;
|
| -
|
| - // Reset interval flag.
|
| - interval.reset();
|
| - skipLevel = false;
|
| - }
|
| -
|
| - /** Restore the player to the game - reseting position etc. */
|
| - void resetPlayerActor(bool persistPowerUps) {
|
| - actors.add([player]);
|
| -
|
| - // Reset the player position.
|
| - player.position.x = GameHandler.width / 2;
|
| - player.position.y = GameHandler.height / 2;
|
| - player.velocity.x = 0.0;
|
| - player.velocity.y = 0.0;
|
| - player.heading = 0.0;
|
| - player.reset(persistPowerUps);
|
| -
|
| - // Reset keyboard input values.
|
| - input.reset();
|
| - }
|
| -
|
| - /** Scene before rendering event handler. */
|
| - void onBeforeRenderScene() {
|
| - // Handle key input.
|
| - if (input.left) {
|
| - // Rotate anti-clockwise.
|
| - player.heading -= 4 * GameHandler.frameMultiplier;
|
| - }
|
| - if (input.right) {
|
| - // Rotate clockwise.
|
| - player.heading += 4 * GameHandler.frameMultiplier;
|
| - }
|
| - if (input.thrust) {
|
| - player.thrust();
|
| - }
|
| - if (input.shield) {
|
| - if (!player.expired()) {
|
| - player.activateShield();
|
| - }
|
| - }
|
| - if (input.fireA) {
|
| - player.firePrimary(playerBullets);
|
| - }
|
| - if (input.fireB) {
|
| - player.fireSecondary(playerBullets);
|
| - }
|
| -
|
| - // Add an enemy every N frames (depending on wave factor).
|
| - // Later waves can have 2 ships on screen - earlier waves have one.
|
| - if (enemyShipCount <= (wave < 5 ? 0 : 1) &&
|
| - GameHandler.frameStart - enemyShipAdded > (20000 - (wave * 1024))) {
|
| - enemies.add(new EnemyShip(this, (wave < 3 ? 0 : randomInt(0, 1))));
|
| - enemyShipCount++;
|
| - enemyShipAdded = GameHandler.frameStart;
|
| - }
|
| -
|
| - // Update all actors using their current vector.
|
| - updateActors();
|
| - }
|
| -
|
| - /** Scene rendering event handler */
|
| - void onRenderScene(CanvasRenderingContext2D ctx) {
|
| - renderActors(ctx);
|
| -
|
| - if (Debug['collisionRadius']) {
|
| - renderCollisionRadius(ctx);
|
| - }
|
| -
|
| - // Render info overlay graphics.
|
| - renderOverlay(ctx);
|
| -
|
| - // Detect bullet collisions.
|
| - collisionDetectBullets();
|
| -
|
| - // Detect player collision with asteroids etc.
|
| - if (!player.expired()) {
|
| - collisionDetectPlayer();
|
| - } else {
|
| - // If the player died, then respawn after a short delay and
|
| - // ensure that they do not instantly collide with an enemy.
|
| - if (GameHandler.frameStart - player.killedOn > 3000) {
|
| - // Perform a test to check no ememy is close to the player.
|
| - var tooClose = false;
|
| - var playerPos =
|
| - new Vector(GameHandler.width * 0.5, GameHandler.height * 0.5);
|
| - for (var i=0, j=this.enemies.length; i<j; i++) {
|
| - var enemy = this.enemies[i];
|
| - if (playerPos.distance(enemy.position) < 80) {
|
| - tooClose = true;
|
| - break;
|
| - }
|
| - }
|
| - if (tooClose == false) {
|
| - resetPlayerActor(false);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - bool isComplete() =>
|
| - (skipLevel || (enemies.length == 0 && effects.length == 0));
|
| -
|
| - void intervalRenderer(Interval interval, CanvasRenderingContext2D ctx) {
|
| - if (interval.framecounter++ < 100) {
|
| - fillText(ctx, interval.label, "18pt Courier New",
|
| - GameHandler.width*0.5 - 48, GameHandler.height*0.5 - 8, "white");
|
| - } else {
|
| - interval.complete = true;
|
| - }
|
| - }
|
| -
|
| - bool onAccelerometer(double x, double y, double z) {
|
| - if (input != null) {
|
| - input.shield =(x > 2.0);
|
| - input.thrust = (x < -1.0);
|
| - input.left = (y < -1.5);
|
| - input.right = (y > 1.5);
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - bool onMouseDownHandler(e) {
|
| - input.fireA = input.fireB = false;
|
| - if (e.clientX < GameHandler.width / 3) input.fireB = true;
|
| - else if (e.clientX > 2 * GameHandler.width / 3) input.fireA = true;
|
| - return true;
|
| - }
|
| -
|
| - bool onMouseUpHandler(e) {
|
| - input.fireA = input.fireB = false;
|
| - return true;
|
| - }
|
| -
|
| - bool onKeyDownHandler(int keyCode) {
|
| - log("In onKeyDownHandler, GameScene");
|
| - switch (keyCode) {
|
| - // Note: GLUT doesn't send key up events,
|
| - // so the emulator sends key events as down/up pairs,
|
| - // which is not what we want. So we have some special
|
| - // numeric key handlers here that are distinct for
|
| - // up and down to support use with GLUT.
|
| - case 52: // '4':
|
| - case Key.LEFT:
|
| - input.left = true;
|
| - return true;
|
| - case 54: // '6'
|
| - case Key.RIGHT:
|
| - input.right = true;
|
| - return true;
|
| - case 56: // '8'
|
| - case Key.UP:
|
| - input.thrust = true;
|
| - return true;
|
| - case 50: // '2'
|
| - case Key.DOWN:
|
| - case Key.SHIFT:
|
| - input.shield = true;
|
| - return true;
|
| - case 48: // '0'
|
| - case Key.SPACE:
|
| - input.fireA = true;
|
| - return true;
|
| - case Key.Z:
|
| - input.fireB = true;
|
| - return true;
|
| -
|
| - case Key.A:
|
| - if (Debug['enabled']) {
|
| - // generate an asteroid
|
| - enemies.add(generateAsteroid(1));
|
| - return true;
|
| - }
|
| - break;
|
| -
|
| - case Key.G:
|
| - if (Debug['enabled']) {
|
| - glowEffectOn = !glowEffectOn;
|
| - return true;
|
| - }
|
| - break;
|
| -
|
| - case Key.L:
|
| - if (Debug['enabled']) {
|
| - skipLevel = true;
|
| - return true;
|
| - }
|
| - break;
|
| -
|
| - case Key.E:
|
| - if (Debug['enabled']) {
|
| - enemies.add(new EnemyShip(this, randomInt(0, 1)));
|
| - return true;
|
| - }
|
| - break;
|
| -
|
| - case Key.ESC:
|
| - GameHandler.togglePause();
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - bool onKeyUpHandler(int keyCode) {
|
| - switch (keyCode) {
|
| - case 53: // '5'
|
| - input.left = false;
|
| - input.right = false;
|
| - input.thrust = false;
|
| - input.shield = false;
|
| - input.fireA = false;
|
| - input.fireB = false;
|
| - return true;
|
| -
|
| - case Key.LEFT:
|
| - input.left = false;
|
| - return true;
|
| - case Key.RIGHT:
|
| - input.right = false;
|
| - return true;
|
| - case Key.UP:
|
| - input.thrust = false;
|
| - return true;
|
| - case Key.DOWN:
|
| - case Key.SHIFT:
|
| - input.shield = false;
|
| - return true;
|
| - case Key.SPACE:
|
| - input.fireA = false;
|
| - return true;
|
| - case Key.Z:
|
| - input.fireB = false;
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - /**
|
| - * Randomly generate a new large asteroid. Ensures the asteroid is not
|
| - * generated too close to the player position!
|
| - */
|
| - Asteroid generateAsteroid(num speedFactor) {
|
| - while (true){
|
| - // perform a test to check it is not too close to the player
|
| - var apos = new Vector(random()*GameHandler.width,
|
| - random()*GameHandler.height);
|
| - if (player.position.distance(apos) > 125) {
|
| - var vec = new Vector( ((random()*2)-1)*speedFactor,
|
| - ((random()*2)-1)*speedFactor );
|
| - return new Asteroid(apos, vec, 4);
|
| - }
|
| - }
|
| - }
|
| -
|
| - /** Update the actors position based on current vectors and expiration. */
|
| - void updateActors() {
|
| - for (var i = 0, j = this.actors.length; i < j; i++) {
|
| - var actorList = this.actors[i];
|
| -
|
| - for (var n = 0; n < actorList.length; n++) {
|
| - var actor = actorList[n];
|
| -
|
| - // call onUpdate() event for each actor
|
| - actor.onUpdate(this);
|
| -
|
| - // expiration test first
|
| - if (actor.expired()) {
|
| - actorList.removeAt(n);
|
| - } else {
|
| - game.updateActorPosition(actor);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Perform the operation needed to destory the player.
|
| - * Mark as killed as reduce lives, explosion effect and play sound.
|
| - */
|
| - void destroyPlayer() {
|
| - // Player destroyed by enemy bullet - remove from play.
|
| - player.kill();
|
| - game.lives--;
|
| - var boom =
|
| - new PlayerExplosion(player.position.clone(), player.velocity.clone());
|
| - effects.add(boom);
|
| - soundManager.play('big_boom');
|
| - }
|
| -
|
| - /**
|
| - * Detect player collisions with various actor classes
|
| - * including Asteroids, Enemies, bullets and collectables
|
| - */
|
| - void collisionDetectPlayer() {
|
| - var playerRadius = player.radius;
|
| - var playerPos = player.position;
|
| -
|
| - // Test circle intersection with each asteroid/enemy ship.
|
| - for (var n = 0, m = enemies.length; n < m; n++) {
|
| - var enemy = enemies[n];
|
| -
|
| - // Calculate distance between the two circles.
|
| - if (playerPos.distance(enemy.position) <= playerRadius + enemy.radius) {
|
| - // Collision detected.
|
| - if (player.isShieldActive()) {
|
| - // Remove thrust from the player vector due to collision.
|
| - player.velocity.scale(0.75);
|
| -
|
| - // Destroy the enemy - the player is invincible with shield up!
|
| - enemy.hit(-1);
|
| - destroyEnemy(enemy, player.velocity, true);
|
| - } else if (!Debug['invincible']) {
|
| - destroyPlayer();
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Test intersection with each enemy bullet.
|
| - for (var i = 0; i < enemyBullets.length; i++) {
|
| - var bullet = enemyBullets[i];
|
| -
|
| - // Calculate distance between the two circles.
|
| - if (playerPos.distance(bullet.position) <= playerRadius + bullet.radius) {
|
| - // Collision detected.
|
| - if (player.isShieldActive()) {
|
| - // Remove this bullet from the actor list as it has been destroyed.
|
| - enemyBullets.removeAt(i);
|
| - } else if (!Debug['invincible']) {
|
| - destroyPlayer();
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Test intersection with each collectable.
|
| - for (var i = 0; i < collectables.length; i++) {
|
| - var item = collectables[i];
|
| -
|
| - // Calculate distance between the two circles.
|
| - if (playerPos.distance(item.position) <= playerRadius + item.radius) {
|
| - // Collision detected - remove item from play and activate it.
|
| - collectables.removeAt(i);
|
| - item.collected(game, player, this);
|
| -
|
| - soundManager.play('powerup');
|
| - }
|
| - }
|
| - }
|
| -
|
| - /** Detect bullet collisions with asteroids and enemy actors. */
|
| - void collisionDetectBullets() {
|
| - var i;
|
| - // Collision detect player bullets with asteroids and enemies.
|
| - for (i = 0; i < playerBullets.length; i++) {
|
| - var bullet = playerBullets[i];
|
| - var bulletRadius = bullet.radius;
|
| - var bulletPos = bullet.position;
|
| -
|
| - // Test circle intersection with each enemy actor.
|
| - var n, m = enemies.length, z;
|
| - for (n = 0; n < m; n++) {
|
| - var enemy = enemies[n];
|
| -
|
| - // Test the distance against the two radius combined.
|
| - if (bulletPos.distance(enemy.position) <= bulletRadius + enemy.radius){
|
| - // intersection detected!
|
| -
|
| - // Test for area effect bomb weapon.
|
| - var effectRad = bullet.effectRadius;
|
| - if (effectRad == 0) {
|
| - // Impact the enemy with the bullet.
|
| - if (enemy.hit(bullet.power)) {
|
| - // Destroy the enemy under the bullet.
|
| - destroyEnemy(enemy, bullet.velocity, true);
|
| - // Randomly release a power up.
|
| - generatePowerUp(enemy);
|
| - } else {
|
| - // Add a bullet impact particle effect to show the hit.
|
| - var effect =
|
| - new PlayerBulletImpact(bullet.position, bullet.velocity);
|
| - effects.add(effect);
|
| - }
|
| - } else {
|
| - // Inform enemy it has been hit by a instant kill weapon.
|
| - enemy.hit(-1);
|
| - generatePowerUp(enemy);
|
| -
|
| - // Add a big explosion actor at the area weapon position and vector.
|
| - var comboCount = 1;
|
| - var boom = new Explosion(
|
| - bullet.position.clone(),
|
| - bullet.velocity.nscale(0.5), 5);
|
| - effects.add(boom);
|
| -
|
| - // Destroy the enemy.
|
| - destroyEnemy(enemy, bullet.velocity, true);
|
| -
|
| - // Wipe out nearby enemies under the weapon effect radius
|
| - // take the length of the enemy actor list here - so we don't
|
| - // kill off -all- baby asteroids - so some elements of the original
|
| - // survive.
|
| - for (var x = 0, z = this.enemies.length, e; x < z; x++) {
|
| - e = enemies[x];
|
| -
|
| - // test the distance against the two radius combined
|
| - if (bulletPos.distance(e.position) <= effectRad + e.radius) {
|
| - e.hit(-1);
|
| - generatePowerUp(e);
|
| - destroyEnemy(e, bullet.velocity, true);
|
| - comboCount++;
|
| - }
|
| - }
|
| -
|
| - // Special score and indicator for "combo" detonation.
|
| - if (comboCount > 4) {
|
| - // Score bonus based on combo size.
|
| - var inc = comboCount * 1000 * wave;
|
| - game.score += inc;
|
| -
|
| - // Generate a special effect indicator at the destroyed
|
| - // enemy position.
|
| - var vec = new Vector(0, -3.0);
|
| - var effect = new ScoreIndicator(
|
| - new Vector(enemy.position.x,
|
| - enemy.position.y - (enemy.size * 8)),
|
| - vec.add(enemy.velocity.nscale(0.5)),
|
| - inc, 16, 'COMBO X ${comboCount}', 'rgb(255,255,55)', 1000);
|
| - effects.add(effect);
|
| -
|
| - // Generate a powerup to reward the player for the combo.
|
| - generatePowerUp(enemy, true);
|
| - }
|
| - }
|
| -
|
| - // Remove this bullet from the actor list as it has been destroyed.
|
| - playerBullets.removeAt(i);
|
| - break;
|
| - }
|
| - }
|
| - }
|
| -
|
| - // collision detect enemy bullets with asteroids
|
| - for (i = 0; i < enemyBullets.length; i++) {
|
| - var bullet = enemyBullets[i];
|
| - var bulletRadius = bullet.radius;
|
| - var bulletPos = bullet.position;
|
| -
|
| - // test circle intersection with each enemy actor
|
| - var n, m = enemies.length, z;
|
| - for (n = 0; n < m; n++) {
|
| - var enemy = enemies[n];
|
| -
|
| - if (enemy is Asteroid) {
|
| - if (bulletPos.distance(enemy.position) <=
|
| - bulletRadius + enemy.radius) {
|
| - // Impact the enemy with the bullet.
|
| - if (enemy.hit(1)) {
|
| - // Destroy the enemy under the bullet.
|
| - destroyEnemy(enemy, bullet.velocity, false);
|
| - } else {
|
| - // Add a bullet impact particle effect to show the hit.
|
| - var effect = new EnemyBulletImpact(bullet.position,
|
| - bullet.velocity);
|
| - effects.add(effect);
|
| - }
|
| -
|
| - // Remove this bullet from the actor list as it has been destroyed.
|
| - enemyBullets.removeAt(i);
|
| - break;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /** Randomly generate a power up to reward the player */
|
| - void generatePowerUp(EnemyActor enemy, [bool force = false]) {
|
| - if (collectables.length < 5 &&
|
| - (force || randomInt(0, ((enemy is Asteroid) ? 25 : 1)) == 0)) {
|
| - // Apply a small random vector in the direction of travel
|
| - // rotate by slightly randomized enemy heading.
|
| - var vec = enemy.velocity.clone();
|
| - var t = new Vector(0.0, -(random() * 2));
|
| - t.rotate(enemy.velocity.theta() * (random() * Math.PI));
|
| - vec.add(t);
|
| -
|
| - // Add a power up to the collectables list.
|
| - collectables.add(new PowerUp(
|
| - new Vector(enemy.position.x, enemy.position.y - (enemy.size * 8)),
|
| - vec));
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Blow up an enemy.
|
| - *
|
| - * An asteroid may generate new baby asteroids and leave an explosion
|
| - * in the wake.
|
| - *
|
| - * Also applies the score for the destroyed item.
|
| - *
|
| - * @param enemy {Game.EnemyActor} The enemy to destory and add score for
|
| - * @param parentVector {Vector} The vector of the item that hit the enemy
|
| - * @param player {boolean} If true, the player was the destroyer
|
| - */
|
| - void destroyEnemy(EnemyActor enemy, Vector parentVector, player) {
|
| - if (enemy is Asteroid) {
|
| - soundManager.play('asteroid_boom${randomInt(1,4)}');
|
| -
|
| - // generate baby asteroids
|
| - generateBabyAsteroids(enemy, parentVector);
|
| -
|
| - // add an explosion at the asteriod position and vector
|
| - var boom = new AsteroidExplosion(
|
| - enemy.position.clone(), enemy.velocity.clone(), enemy);
|
| - effects.add(boom);
|
| -
|
| - if (player!= null) {
|
| - // increment score based on asteroid size
|
| - var inc = ((5 - enemy.size) * 4) * 100 * wave;
|
| - game.score += inc;
|
| -
|
| - // generate a score effect indicator at the destroyed enemy position
|
| - var vec = new Vector(0, -1.5).add(enemy.velocity.nscale(0.5));
|
| - var effect = new ScoreIndicator(
|
| - new Vector(enemy.position.x, enemy.position.y -
|
| - (enemy.size * 8)), vec, inc);
|
| - effects.add(effect);
|
| - }
|
| - } else if (enemy is EnemyShip) {
|
| - soundManager.play('asteroid_boom1');
|
| -
|
| - // add an explosion at the enemy ship position and vector
|
| - var boom = new EnemyExplosion(enemy.position.clone(),
|
| - enemy.velocity.clone(), enemy);
|
| - effects.add(boom);
|
| -
|
| - if (player != null) {
|
| - // increment score based on asteroid size
|
| - var inc = 2000 * wave * (enemy.size + 1);
|
| - game.score += inc;
|
| -
|
| - // generate a score effect indicator at the destroyed enemy position
|
| - var vec = new Vector(0, -1.5).add(enemy.velocity.nscale(0.5));
|
| - var effect = new ScoreIndicator(
|
| - new Vector(enemy.position.x, enemy.position.y - 16),
|
| - vec, inc);
|
| - effects.add(effect);
|
| - }
|
| -
|
| - // decrement scene ship count
|
| - enemyShipCount--;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Generate a number of baby asteroids from a detonated parent asteroid.
|
| - * The number and size of the generated asteroids are based on the parent
|
| - * size. Some of the momentum of the parent vector (e.g. impacting bullet)
|
| - * is applied to the new asteroids.
|
| - */
|
| - void generateBabyAsteroids(Asteroid asteroid, Vector parentVector) {
|
| - // generate some baby asteroid(s) if bigger than the minimum size
|
| - if (asteroid.size > 1) {
|
| - var xc=randomInt(asteroid.size ~/ 2, asteroid.size - 1);
|
| - for (var x=0; x < xc; x++) {
|
| - var babySize = randomInt(1, asteroid.size - 1);
|
| -
|
| - var vec = asteroid.velocity.clone();
|
| -
|
| - // apply a small random vector in the direction of travel
|
| - var t = new Vector(0.0, -random());
|
| -
|
| - // rotate vector by asteroid current heading - slightly randomized
|
| - t.rotate(asteroid.velocity.theta() * (random() * Math.PI));
|
| - vec.add(t);
|
| -
|
| - // add the scaled parent vector - to give some momentum from the impact
|
| - vec.add(parentVector.nscale(0.2));
|
| -
|
| - // create the asteroid - slightly offset from the centre of the old one
|
| - var baby = new Asteroid(
|
| - new Vector(asteroid.position.x + (random()*5)-2.5,
|
| - asteroid.position.y + (random()*5)-2.5),
|
| - vec, babySize, asteroid.type);
|
| - enemies.add(baby);
|
| - }
|
| - }
|
| - }
|
| -
|
| - /** Render each actor to the canvas. */
|
| - void renderActors(CanvasRenderingContext2D ctx){
|
| - for (var i = 0, j = actors.length; i < j; i++) {
|
| - // walk each sub-list and call render on each object
|
| - var actorList = actors[i];
|
| -
|
| - for (var n = actorList.length - 1; n >= 0; n--) {
|
| - actorList[n].onRender(ctx);
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * DEBUG - Render the radius of the collision detection circle around
|
| - * each actor.
|
| - */
|
| - void renderCollisionRadius(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.strokeStyle = "rgb(255,0,0)";
|
| - ctx.lineWidth = 0.5;
|
| - ctx.shadowBlur = 0;
|
| -
|
| - for (var i = 0, j = actors.length; i < j; i++) {
|
| - var actorList = actors[i];
|
| -
|
| - for (var n = actorList.length - 1, actor; n >= 0; n--) {
|
| - actor = actorList[n];
|
| - if (actor.radius) {
|
| - ctx.beginPath();
|
| - ctx.arc(actor.position.x, actor.position.y, actor.radius, 0,
|
| - TWOPI, true);
|
| - ctx.closePath();
|
| - ctx.stroke();
|
| - }
|
| - }
|
| - }
|
| - ctx.restore();
|
| - }
|
| -
|
| - /**
|
| - * Render player information HUD overlay graphics.
|
| - *
|
| - * @param ctx {object} Canvas rendering context
|
| - */
|
| - void renderOverlay(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.shadowBlur = 0;
|
| -
|
| - // energy bar (100 pixels across, scaled down from player energy max)
|
| - ctx.strokeStyle = "rgb(50,50,255)";
|
| - ctx.strokeRect(4, 4, 101, 6);
|
| - ctx.fillStyle = "rgb(100,100,255)";
|
| - var energy = player.energy;
|
| - if (energy > player.ENERGY_INIT) {
|
| - // the shield is on for "free" briefly when he player respawns
|
| - energy = player.ENERGY_INIT;
|
| - }
|
| - ctx.fillRect(5, 5, (energy / (player.ENERGY_INIT / 100)), 5);
|
| -
|
| - // lives indicator graphics
|
| - for (var i=0; i<game.lives; i++) {
|
| - drawScaledImage(ctx, _playerImg, 0, 0, 64,
|
| - 350+(i*20), 0, 16);
|
| -
|
| - // score display - update towards the score in increments to animate it
|
| - var score = game.score;
|
| - var inc = (score - scoredisplay) ~/ 10;
|
| - scoredisplay += inc;
|
| - if (scoredisplay > score) {
|
| - scoredisplay = score;
|
| - }
|
| - var sscore = scoredisplay.ceil().toString();
|
| - // pad with zeros
|
| - for (var i=0, j=8-sscore.length; i<j; i++) {
|
| - sscore = "0${sscore}";
|
| - }
|
| - fillText(ctx, sscore, "12pt Courier New", 120, 12, "white");
|
| -
|
| - // high score
|
| - // TODO: add method for incrementing score so this is not done here
|
| - if (score > game.highscore) {
|
| - game.highscore = score;
|
| - }
|
| - sscore = game.highscore.toString();
|
| - // pad with zeros
|
| - for (var i=0, j=8-sscore.length; i<j; i++) {
|
| - sscore = "0${sscore}";
|
| - }
|
| - fillText(ctx, "HI: ${sscore}", "12pt Courier New", 220, 12, "white");
|
| -
|
| - // debug output
|
| - if (Debug['fps']) {
|
| - fillText(ctx, "FPS: ${GameHandler.maxfps}", "12pt Courier New",
|
| - 0, GameHandler.height - 2, "lightblue");
|
| - }
|
| - }
|
| - ctx.restore();
|
| - }
|
| -}
|
| -
|
| -class Interval {
|
| - String label;
|
| - Function intervalRenderer;
|
| - int framecounter = 0;
|
| - bool complete = false;
|
| -
|
| - Interval([this.label = null, this.intervalRenderer = null]);
|
| -
|
| - void reset() {
|
| - framecounter = 0;
|
| - complete = false;
|
| - }
|
| -}
|
| -
|
| -class Bullet extends ShortLivedActor {
|
| -
|
| - Bullet(Vector position, Vector velocity,
|
| - [this.heading = 0.0, int lifespan = 1300])
|
| - : super(position, velocity, lifespan) {
|
| - }
|
| -
|
| - const BULLET_WIDTH = 2;
|
| - const BULLET_HEIGHT = 6;
|
| - const FADE_LENGTH = 200;
|
| -
|
| - double heading;
|
| - int _power = 1;
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - // hack to stop draw under player graphic
|
| - if (frameStart - start > 40) {
|
| - ctx.save();
|
| - ctx.globalCompositeOperation = "lighter";
|
| - ctx.globalAlpha = fadeValue(1.0, FADE_LENGTH);
|
| - // rotate the bullet bitmap into the correct heading
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate(heading * RAD);
|
| - // TODO(gram) - figure out how to get rid of the vector art so we don't
|
| - // need the [0] below.
|
| - ctx.drawImage(GameHandler.bitmaps.images["bullet"],
|
| - -(BULLET_WIDTH + GLOWSHADOWBLUR*2)*0.5,
|
| - -(BULLET_HEIGHT + GLOWSHADOWBLUR*2)*0.5);
|
| - ctx.restore();
|
| - }
|
| - }
|
| -
|
| - /** Area effect weapon radius - zero for primary bullets. */
|
| - get effectRadius => 0;
|
| -
|
| - // approximate based on average between width and height
|
| - get radius => 4;
|
| -
|
| - get power => _power;
|
| -}
|
| -
|
| -/**
|
| - * Player BulletX2 actor class. Used by the TwinCannons primary weapon.
|
| - */
|
| -class BulletX2 extends Bullet {
|
| -
|
| - BulletX2(Vector position, Vector vector, double heading)
|
| - : super(position, vector, heading, 1750) {
|
| - _power = 2;
|
| - }
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - // hack to stop draw under player graphic
|
| - if (frameStart - start > 40) {
|
| - ctx.save();
|
| - ctx.globalCompositeOperation = "lighter";
|
| - ctx.globalAlpha = fadeValue(1.0, FADE_LENGTH);
|
| - // rotate the bullet bitmap into the correct heading
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate(heading * RAD);
|
| - ctx.drawImage(GameHandler.bitmaps.images["bulletx2"],
|
| - -(BULLET_WIDTH + GLOWSHADOWBLUR*4) / 2,
|
| - -(BULLET_HEIGHT + GLOWSHADOWBLUR*2) / 2);
|
| - ctx.restore();
|
| - }
|
| - }
|
| -
|
| - get radius => BULLET_HEIGHT;
|
| -}
|
| -
|
| -class Bomb extends Bullet {
|
| - Bomb(Vector position, Vector velocity)
|
| - : super(position, velocity, 0.0, 3000);
|
| -
|
| - const BOMB_RADIUS = 4.0;
|
| - const FADE_LENGTH = 200;
|
| - const EFFECT_RADIUS = 45;
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.globalCompositeOperation = "lighter";
|
| - ctx.globalAlpha = fadeValue(1.0, FADE_LENGTH);
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate((frameStart % (360*32)) / 32);
|
| - var scale = fadeValue(1.0, FADE_LENGTH);
|
| - if (scale <= 0) scale = 0.01;
|
| - ctx.scale(scale, scale);
|
| - ctx.drawImage(GameHandler.bitmaps.images["bomb"],
|
| - -(BOMB_RADIUS + GLOWSHADOWBLUR),
|
| - -(BOMB_RADIUS + GLOWSHADOWBLUR));
|
| - ctx.restore();
|
| - }
|
| -
|
| - get effectRadius => EFFECT_RADIUS;
|
| - get radius => fadeValue(BOMB_RADIUS, FADE_LENGTH);
|
| -}
|
| -
|
| -class EnemyBullet extends Bullet {
|
| - EnemyBullet(Vector position, Vector velocity)
|
| - : super(position, velocity, 0.0, 2800);
|
| -
|
| - const BULLET_RADIUS = 4.0;
|
| - const FADE_LENGTH = 200;
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.globalAlpha = fadeValue(1.0, FADE_LENGTH);
|
| - ctx.globalCompositeOperation = "lighter";
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate((frameStart % (360*64)) / 64);
|
| - var scale = fadeValue(1.0, FADE_LENGTH);
|
| - if (scale <= 0) scale = 0.01;
|
| - ctx.scale(scale, scale);
|
| - ctx.drawImage(GameHandler.bitmaps.images["enemybullet"],
|
| - -(BULLET_RADIUS + GLOWSHADOWBLUR),
|
| - -(BULLET_RADIUS + GLOWSHADOWBLUR));
|
| - ctx.restore();
|
| - }
|
| -
|
| - get radius => fadeValue(BULLET_RADIUS, FADE_LENGTH) + 1;
|
| -}
|
| -
|
| -class Particle extends ShortLivedActor {
|
| - int size;
|
| - int type;
|
| - int fadelength;
|
| - String color;
|
| - double rotate;
|
| - double rotationv;
|
| -
|
| - Particle(Vector position, Vector velocity, this.size, this.type,
|
| - int lifespan, this.fadelength,
|
| - [this.color = Colors.PARTICLE])
|
| - : super(position, velocity, lifespan) {
|
| -
|
| - // randomize rotation speed and angle for line particle
|
| - if (type == 1) {
|
| - rotate = random() * TWOPI;
|
| - rotationv = (random() - 0.5) * 0.5;
|
| - }
|
| - }
|
| -
|
| - bool update() {
|
| - position.add(velocity);
|
| - return !expired();
|
| - }
|
| -
|
| - void render(CanvasRenderingContext2D ctx) {
|
| - ctx.globalAlpha = fadeValue(1.0, fadelength);
|
| - switch (type) {
|
| - case 0: // point (prerendered image)
|
| - ctx.translate(position.x, position.y);
|
| - ctx.drawImage(
|
| - GameHandler.bitmaps.images["points_${color}"][size], 0, 0);
|
| - break;
|
| - // TODO: prerender a glowing line to use as the particle!
|
| - case 1: // line
|
| - ctx.translate(position.x, position.y);
|
| - var s = size;
|
| - ctx.rotate(rotate);
|
| - this.rotate += rotationv;
|
| - ctx.strokeStyle = color;
|
| - ctx.lineWidth = 1.5;
|
| - ctx.beginPath();
|
| - ctx.moveTo(-s, -s);
|
| - ctx.lineTo(s, s);
|
| - ctx.closePath();
|
| - ctx.stroke();
|
| - break;
|
| - case 2: // smudge (prerendered image)
|
| - var offset = (size + 1) << 2;
|
| - renderImage(ctx,
|
| - GameHandler.bitmaps.images["smudges_${color}"][size],
|
| - 0, 0, (size + 1) << 3,
|
| - position.x - offset, position.y - offset, (size + 1) << 3);
|
| - break;
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Particle emitter effect actor class.
|
| - *
|
| - * A simple particle emitter, that does not recycle particles, but sets itself
|
| - * as expired() once all child particles have expired.
|
| - *
|
| - * Requires a function known as the emitter that is called per particle
|
| - * generated.
|
| - */
|
| -class ParticleEmitter extends Actor {
|
| -
|
| - List<Particle> particles;
|
| -
|
| - ParticleEmitter(Vector position, Vector velocity)
|
| - : super(position, velocity);
|
| -
|
| - Particle emitter() {}
|
| -
|
| - void init(count) {
|
| - // generate particles based on the supplied emitter function
|
| - particles = [];
|
| - for (var i = 0; i < count; i++) {
|
| - particles.add(emitter());
|
| - }
|
| - }
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - ctx.save();
|
| - ctx.shadowBlur = 0;
|
| - ctx.globalCompositeOperation = "lighter";
|
| - for (var i=0, particle; i < particles.length; i++) {
|
| - particle = particles[i];
|
| -
|
| - // update particle and test for lifespan
|
| - if (particle.update()) {
|
| - ctx.save();
|
| - particle.render(ctx);
|
| - ctx.restore();
|
| - } else {
|
| - // particle no longer alive, remove from list
|
| - particles.removeAt(i);
|
| - }
|
| - }
|
| - ctx.restore();
|
| - }
|
| -
|
| - bool expired() => (particles.length == 0);
|
| -}
|
| -
|
| -class AsteroidExplosion extends ParticleEmitter {
|
| - var asteroid;
|
| -
|
| - AsteroidExplosion(Vector position, Vector vector, this.asteroid)
|
| - : super(position, vector) {
|
| - init(asteroid.size*2);
|
| - }
|
| -
|
| - Particle emitter() {
|
| - // Randomise radial direction vector - speed and angle, then add parent
|
| - // vector.
|
| - var pos = position.clone();
|
| - if (random() < 0.5) {
|
| - var t = new Vector(0, randomInt(5, 10));
|
| - t.rotate(random() * TWOPI).add(velocity);
|
| - return new Particle(pos, t, (random() * 4).floor(), 0, 400, 300);
|
| - } else {
|
| - var t = new Vector(0, randomInt(1, 3));
|
| - t.rotate(random() * TWOPI).add(velocity);
|
| - return new Particle(pos, t,
|
| - (random() * 4).floor() + asteroid.size, 2, 500, 250);
|
| - }
|
| - }
|
| -}
|
| -
|
| -class PlayerExplosion extends ParticleEmitter {
|
| - PlayerExplosion(Vector position, Vector vector)
|
| - : super(position, vector) {
|
| - init(12);
|
| - }
|
| -
|
| - Particle emitter() {
|
| - // Randomise radial direction vector - speed and angle, then add
|
| - // parent vector.
|
| - var pos = position.clone();
|
| - if (random() < 0.5){
|
| - var t = new Vector(0, randomInt(5, 10));
|
| - t.rotate(random() * TWOPI).add(velocity);
|
| - return new Particle(pos, t, (random() * 4).floor(), 0, 400, 300);
|
| - } else {
|
| - var t = new Vector(0, randomInt(1, 3));
|
| - t.rotate(random() * TWOPI).add(velocity);
|
| - return new Particle(pos, t, (random() * 4).floor() + 2, 2, 500, 250);
|
| - }
|
| - }
|
| -}
|
| -
|
| -/** Enemy particle based explosion - Particle effect actor class. */
|
| -class EnemyExplosion extends ParticleEmitter {
|
| - var enemy;
|
| - EnemyExplosion(Vector position, Vector vector, this.enemy)
|
| - : super(position, vector) {
|
| - init(8);
|
| - }
|
| -
|
| - Particle emitter() {
|
| - // randomise radial direction vector - speed and angle, then
|
| - // add parent vector.
|
| - var pos = position.clone();
|
| - if (random() < 0.5) {
|
| - var t = new Vector(0, randomInt(5, 10));
|
| - t.rotate(random() * TWOPI).add(velocity);
|
| - return new Particle(pos, t, (random() * 4).floor(), 0,
|
| - 400, 300, Colors.ENEMY_SHIP);
|
| - } else {
|
| - var t = new Vector(0, randomInt(1, 3));
|
| - t.rotate(random() * 2 * TWOPI).add(velocity);
|
| - return new Particle(pos, t,
|
| - (random() * 4).floor() + (enemy.size == 0 ? 2 : 0), 2,
|
| - 500, 250, Colors.ENEMY_SHIP);
|
| - }
|
| - }
|
| -}
|
| -
|
| -class Explosion extends EffectActor {
|
| -/**
|
| - * Basic explosion effect actor class.
|
| - *
|
| - * TODO: replace all instances of this with particle effects
|
| - * - this is still usedby the smartbomb
|
| - */
|
| - Explosion(Vector position, Vector vector, this.size)
|
| - : super(position, vector, FADE_LENGTH);
|
| -
|
| - static const FADE_LENGTH = 300;
|
| -
|
| - num size = 0;
|
| -
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - // fade out
|
| - var brightness = (effectValue(255.0)).floor(),
|
| - rad = effectValue(size * 8.0),
|
| - rgb = brightness.toString();
|
| - ctx.save();
|
| - ctx.globalAlpha = 0.75;
|
| - ctx.fillStyle = "rgb(${rgb},0,0)";
|
| - ctx.beginPath();
|
| - ctx.arc(position.x, position.y, rad, 0, TWOPI, true);
|
| - ctx.closePath();
|
| - ctx.fill();
|
| - ctx.restore();
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Player bullet impact effect - Particle effect actor class.
|
| - * Used when an enemy is hit by player bullet but not destroyed.
|
| - */
|
| -class PlayerBulletImpact extends ParticleEmitter {
|
| - PlayerBulletImpact(Vector position, Vector vector)
|
| - : super(position, vector) {
|
| - init(5);
|
| - }
|
| -
|
| - Particle emitter() {
|
| - // slightly randomise vector angle - then add parent vector
|
| - var t = velocity.nscale(0.75 + random() * 0.5);
|
| - t.rotate(random() * PIO4 - PIO8);
|
| - return new Particle(position.clone(), t,
|
| - (random() * 4).floor(), 0, 250, 150, Colors.GREEN_LASER);
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Enemy bullet impact effect - Particle effect actor class.
|
| - * Used when an enemy is hit by player bullet but not destroyed.
|
| - */
|
| -class EnemyBulletImpact extends ParticleEmitter {
|
| - EnemyBulletImpact(Vector position , Vector vector)
|
| - : super(position, vector) {
|
| - init(5);
|
| - }
|
| -
|
| - Particle emitter() {
|
| - // slightly randomise vector angle - then add parent vector
|
| - var t = velocity.nscale(0.75 + random() * 0.5);
|
| - t.rotate(random() * PIO4 - PIO8);
|
| - return new Particle(position.clone(), t,
|
| - (random() * 4).floor(), 0, 250, 150, Colors.ENEMY_SHIP);
|
| - }
|
| -}
|
| -
|
| -class Player extends SpriteActor {
|
| - Player(Vector position, Vector vector, this.heading)
|
| - : super(position, vector) {
|
| - energy = ENERGY_INIT;
|
| -
|
| - // setup SpriteActor values - used for shield sprite
|
| - animImage = _shieldImg;
|
| - animLength = SHIELD_ANIM_LENGTH;
|
| -
|
| - // setup weapons
|
| - primaryWeapons = {};
|
| - }
|
| -
|
| - const MAX_PLAYER_VELOCITY = 8.0;
|
| - const PLAYER_RADIUS = 9;
|
| - const SHIELD_RADIUS = 14;
|
| - const SHIELD_ANIM_LENGTH = 100;
|
| - const SHIELD_MIN_PULSE = 20;
|
| - const ENERGY_INIT = 400;
|
| - const THRUST_DELAY_MS = 100;
|
| - const BOMB_RECHARGE_MS = 800;
|
| - const BOMB_ENERGY = 80;
|
| -
|
| - double heading = 0.0;
|
| -
|
| - /** Player energy (shield and bombs). */
|
| - num energy = 0;
|
| -
|
| - /** Player shield active counter. */
|
| - num shieldCounter = 0;
|
| -
|
| - bool alive = true;
|
| - Map primaryWeapons = null;
|
| -
|
| - /** Bomb fire recharging counter. */
|
| - num bombRecharge = 0;
|
| -
|
| - /** Engine thrust recharge counter. */
|
| - num thrustRecharge = 0;
|
| -
|
| - /** True if the engine thrust graphics should be rendered next frame. */
|
| - bool engineThrust = false;
|
| -
|
| - /**
|
| - * Time that the player was killed - to cause a delay before respawning
|
| - * the player
|
| - */
|
| - num killedOn = 0;
|
| -
|
| - bool fireWhenShield = false;
|
| -
|
| - /** Player rendering method
|
| - *
|
| - * @param ctx {object} Canvas rendering context
|
| - */
|
| - void onRender(CanvasRenderingContext2D ctx) {
|
| - var headingRad = heading * RAD;
|
| -
|
| - // render engine thrust?
|
| - if (engineThrust) {
|
| - ctx.save();
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate(headingRad);
|
| - ctx.globalAlpha = 0.5 + random() * 0.5;
|
| - ctx.globalCompositeOperation = "lighter";
|
| - ctx.fillStyle = Colors.PLAYER_THRUST;
|
| - ctx.beginPath();
|
| - ctx.moveTo(-5, 8);
|
| - ctx.lineTo(5, 8);
|
| - ctx.lineTo(0, 18 + random() * 6);
|
| - ctx.closePath();
|
| - ctx.fill();
|
| - ctx.restore();
|
| - engineThrust = false;
|
| - }
|
| -
|
| - // render player graphic
|
| - var size = (PLAYER_RADIUS * 2) + 6;
|
| - // normalise the player heading to 0-359 degrees
|
| - // then locate the correct frame in the sprite strip -
|
| - // an image for each 4 degrees of rotation
|
| - var normAngle = heading.floor() % 360;
|
| - if (normAngle < 0) {
|
| - normAngle = 360 + normAngle;
|
| - }
|
| - ctx.save();
|
| - drawScaledImage(ctx, _playerImg,
|
| - 0, (normAngle / 4).floor() * 64, 64,
|
| - position.x - (size / 2), position.y - (size / 2), size);
|
| - ctx.restore();
|
| -
|
| - // shield up? if so render a shield graphic around the ship
|
| - if (shieldCounter > 0 && energy > 0) {
|
| - // render shield graphic bitmap
|
| - ctx.save();
|
| - ctx.translate(position.x, position.y);
|
| - ctx.rotate(headingRad);
|
| - renderSprite(ctx, -SHIELD_RADIUS-1,
|
| - -SHIELD_RADIUS-1, (SHIELD_RADIUS * 2) + 2);
|
| - ctx.restore();
|
| -
|
| - shieldCounter--;
|
| - energy -= 1.5;
|
| - }
|
| - }
|
| -
|
| - /** Execute player forward thrust request. */
|
| - void thrust() {
|
| - // now test we did not thrust too recently, based on time since last thrust
|
| - // request - ensures same thrust at any framerate
|
| - if (frameStart - thrustRecharge > THRUST_DELAY_MS) {
|
| - // update last thrust time
|
| - thrustRecharge = frameStart;
|
| -
|
| - // generate a small thrust vector
|
| - var t = new Vector(0.0, -0.5);
|
| -
|
| - // rotate thrust vector by player current heading
|
| - t.rotate(heading * RAD);
|
| -
|
| - // add player thrust vector to position
|
| - velocity.add(t);
|
| -
|
| - // player can't exceed maximum velocity - scale vector down if
|
| - // this occurs - do this rather than not adding the thrust at all
|
| - // otherwise the player cannot turn and thrust at max velocity
|
| - if (velocity.length() > MAX_PLAYER_VELOCITY) {
|
| - velocity.scale(MAX_PLAYER_VELOCITY / velocity.length());
|
| - }
|
| - }
|
| - // mark so that we know to render engine thrust graphics
|
| - engineThrust = true;
|
| - }
|
| -
|
| - /**
|
| - * Execute player active shield request.
|
| - * If energy remaining the shield will be briefly applied.
|
| - */
|
| - void activateShield() {
|
| - // ensure shield stays up for a brief pulse between key presses!
|
| - if (energy >= SHIELD_MIN_PULSE) {
|
| - shieldCounter = SHIELD_MIN_PULSE;
|
| - }
|
| - }
|
| -
|
| - bool isShieldActive() => (shieldCounter > 0 && energy > 0);
|
| -
|
| - get radius => (isShieldActive() ? SHIELD_RADIUS : PLAYER_RADIUS);
|
| -
|
| - bool expired() => !(alive);
|
| -
|
| - void kill() {
|
| - alive = false;
|
| - killedOn = frameStart;
|
| - }
|
| -
|
| - /** Fire primary weapon(s). */
|
| -
|
| - void firePrimary(List bulletList) {
|
| - var playedSound = false;
|
| - // attempt to fire the primary weapon(s)
|
| - // first ensure player is alive and the shield is not up
|
| - if (alive && (!isShieldActive() || fireWhenShield)) {
|
| - for (var w in primaryWeapons.keys) {
|
| - var b = primaryWeapons[w].fire();
|
| - if (b != null) {
|
| - for (var i=0; i<b.length; i++) {
|
| - bulletList.add(b[i]);
|
| - }
|
| - if (!playedSound) {
|
| - soundManager.play('laser');
|
| - playedSound = true;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Fire secondary weapon.
|
| - * @param bulletList {Array} to add bullet to on success
|
| - */
|
| - void fireSecondary(List bulletList) {
|
| - // Attempt to fire the secondary weapon and generate bomb object if
|
| - // successful. First ensure player is alive and the shield is not up.
|
| - if (alive && (!isShieldActive() || fireWhenShield) && energy > BOMB_ENERGY){
|
| - // now test we did not fire too recently
|
| - if (frameStart - bombRecharge > BOMB_RECHARGE_MS) {
|
| - // ok, update last fired time and we can now generate a bomb
|
| - bombRecharge = frameStart;
|
| -
|
| - // decrement energy supply
|
| - energy -= BOMB_ENERGY;
|
| -
|
| - // generate a vector rotated to the player heading and then add the
|
| - // current player vector to give the bomb the correct directional
|
| - // momentum.
|
| - var t = new Vector(0.0, -3.0);
|
| - t.rotate(heading * RAD);
|
| - t.add(velocity);
|
| -
|
| - bulletList.add(new Bomb(position.clone(), t));
|
| - }
|
| - }
|
| - }
|
| -
|
| - void onUpdate(_) {
|
| - // slowly recharge the shield - if not active
|
| - if (!isShieldActive() && energy < ENERGY_INIT) {
|
| - energy += 0.1;
|
| - }
|
| - }
|
| -
|
| - void reset(bool persistPowerUps) {
|
| - // reset energy, alive status, weapons and power up flags
|
| - alive = true;
|
| - if (!persistPowerUps) {
|
| - primaryWeapons = {};
|
| - primaryWeapons["main"] = new PrimaryWeapon(this);
|
| - fireWhenShield = false;
|
| - }
|
| - energy = ENERGY_INIT + SHIELD_MIN_PULSE; // for shield as below
|
| -
|
| - // active shield briefly
|
| - activateShield();
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Image Preloader class. Executes the supplied callback function once all
|
| - * registered images are loaded by the browser.
|
| - */
|
| -class Preloader {
|
| - Preloader() {
|
| - images = new List();
|
| - }
|
| -
|
| - /**
|
| - * Image list
|
| - *
|
| - * @property images
|
| - * @type Array
|
| - */
|
| - var images = [];
|
| -
|
| - /**
|
| - * Callback function
|
| - *
|
| - * @property callback
|
| - * @type Function
|
| - */
|
| - var callback = null;
|
| -
|
| - /**
|
| - * Images loaded so far counter
|
| - */
|
| - var counter = 0;
|
| -
|
| - /**
|
| - * Add an image to the list of images to wait for
|
| - */
|
| - void addImage(ImageElement img, String url) {
|
| - var me = this;
|
| - img.src = url;
|
| - // attach closure to the image onload handler
|
| - img.onLoad.listen((_) {
|
| - me.counter++;
|
| - if (me.counter == me.images.length) {
|
| - // all images are loaded - execute callback function
|
| - me.callback();
|
| - }
|
| - });
|
| - images.add(img);
|
| - }
|
| -
|
| - /**
|
| - * Load the images and call the supplied function when ready
|
| - */
|
| - void onLoadCallback(Function fn) {
|
| - counter = 0;
|
| - callback = fn;
|
| - // load the images
|
| - //for (var i=0, j = images.length; i<j; i++) {
|
| - // images[i].src = images[i].url;
|
| - //}
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Game prerenderer class.
|
| - */
|
| -class GamePrerenderer {
|
| - GamePrerenderer();
|
| -
|
| - /**
|
| - * Image list. Keyed by renderer ID - returning an array also. So to get
|
| - * the first image output by prerenderer with id "default":
|
| - * images["default"][0]
|
| - */
|
| - Map images = {};
|
| - Map _renderers = {};
|
| -
|
| - /** Add a renderer function to the list of renderers to execute. */
|
| - addRenderer(Function fn, String id) => _renderers[id] = fn;
|
| -
|
| -
|
| - /** Execute all prerender functions. */
|
| - void execute() {
|
| - var buffer = new CanvasElement();
|
| - for (var id in _renderers.keys) {
|
| - images[id] = _renderers[id](buffer);
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Asteroids prerenderer class.
|
| - *
|
| - * Encapsulates the early rendering of various effects used in the game. Each
|
| - * effect is rendered once to a hidden canvas object, the image data is
|
| - * extracted and stored in an Image object - which can then be reused later.
|
| - * This is much faster than rendering each effect again and again at runtime.
|
| - *
|
| - * The downside to this is that some constants are duplicated here and in the
|
| - * original classes - so updates to the original classes such as the weapon
|
| - * effects must be duplicated here.
|
| - */
|
| -class Prerenderer extends GamePrerenderer {
|
| - Prerenderer() : super() {
|
| -
|
| - // function to generate a set of point particle images
|
| - var fnPointRenderer = (CanvasElement buffer, String color) {
|
| - var imgs = [];
|
| - for (var size = 3; size <= 6; size++) {
|
| - var width = size << 1;
|
| - buffer.width = buffer.height = width;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| - var radgrad = ctx.createRadialGradient(size, size, size >> 1,
|
| - size, size, size);
|
| - radgrad.addColorStop(0, color);
|
| - radgrad.addColorStop(1, "#000");
|
| - ctx.fillStyle = radgrad;
|
| - ctx.fillRect(0, 0, width, width);
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - imgs.add(img);
|
| - }
|
| - return imgs;
|
| - };
|
| -
|
| - // add the various point particle image prerenderers based on above function
|
| - // default explosion color
|
| - addRenderer((CanvasElement buffer) {
|
| - return fnPointRenderer(buffer, Colors.PARTICLE);
|
| - }, "points_${Colors.PARTICLE}");
|
| -
|
| - // player bullet impact particles
|
| - addRenderer((CanvasElement buffer) {
|
| - return fnPointRenderer(buffer, Colors.GREEN_LASER);
|
| - }, "points_${Colors.GREEN_LASER}");
|
| -
|
| - // enemy bullet impact particles
|
| - addRenderer((CanvasElement buffer) {
|
| - return fnPointRenderer(buffer, Colors.ENEMY_SHIP);
|
| - }, "points_${Colors.ENEMY_SHIP}");
|
| -
|
| - // add the smudge explosion particle image prerenderer
|
| - var fnSmudgeRenderer = (CanvasElement buffer, String color) {
|
| - var imgs = [];
|
| - for (var size = 4; size <= 32; size += 4) {
|
| - var width = size << 1;
|
| - buffer.width = buffer.height = width;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| - var radgrad = ctx.createRadialGradient(size, size, size >> 3,
|
| - size, size, size);
|
| - radgrad.addColorStop(0, color);
|
| - radgrad.addColorStop(1, "#000");
|
| - ctx.fillStyle = radgrad;
|
| - ctx.fillRect(0, 0, width, width);
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - imgs.add(img);
|
| - }
|
| - return imgs;
|
| - };
|
| -
|
| - addRenderer((CanvasElement buffer) {
|
| - return fnSmudgeRenderer(buffer, Colors.PARTICLE);
|
| - }, "smudges_${Colors.PARTICLE}");
|
| -
|
| - addRenderer((CanvasElement buffer) {
|
| - return fnSmudgeRenderer(buffer, Colors.ENEMY_SHIP);
|
| - }, "smudges_${Colors.ENEMY_SHIP}");
|
| -
|
| - // standard player bullet
|
| - addRenderer((CanvasElement buffer) {
|
| - // NOTE: keep in sync with Asteroids.Bullet
|
| - var BULLET_WIDTH = 2, BULLET_HEIGHT = 6;
|
| - var imgs = [];
|
| - buffer.width = BULLET_WIDTH + GLOWSHADOWBLUR*2;
|
| - buffer.height = BULLET_HEIGHT + GLOWSHADOWBLUR*2;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| -
|
| - var rf = (width, height) {
|
| - ctx.beginPath();
|
| - ctx.moveTo(0, height);
|
| - ctx.lineTo(width, 0);
|
| - ctx.lineTo(0, -height);
|
| - ctx.lineTo(-width, 0);
|
| - ctx.closePath();
|
| - };
|
| -
|
| - ctx.shadowBlur = GLOWSHADOWBLUR;
|
| - ctx.translate(buffer.width * 0.5, buffer.height * 0.5);
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASER_DARK;
|
| - rf(BULLET_WIDTH-1, BULLET_HEIGHT-1);
|
| - ctx.fill();
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASER;
|
| - rf(BULLET_WIDTH, BULLET_HEIGHT);
|
| - ctx.fill();
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - return img;
|
| - }, "bullet");
|
| -
|
| - // player bullet X2
|
| - addRenderer((CanvasElement buffer) {
|
| - // NOTE: keep in sync with Asteroids.BulletX2
|
| - var BULLET_WIDTH = 2, BULLET_HEIGHT = 6;
|
| - buffer.width = BULLET_WIDTH + GLOWSHADOWBLUR*4;
|
| - buffer.height = BULLET_HEIGHT + GLOWSHADOWBLUR*2;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| -
|
| - var rf = (width, height) {
|
| - ctx.beginPath();
|
| - ctx.moveTo(0, height);
|
| - ctx.lineTo(width, 0);
|
| - ctx.lineTo(0, -height);
|
| - ctx.lineTo(-width, 0);
|
| - ctx.closePath();
|
| - };
|
| -
|
| - ctx.shadowBlur = GLOWSHADOWBLUR;
|
| - ctx.translate(buffer.width * 0.5, buffer.height * 0.5);
|
| - ctx.save();
|
| - ctx.translate(-4, 0);
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASERX2_DARK;
|
| - rf(BULLET_WIDTH-1, BULLET_HEIGHT-1);
|
| - ctx.fill();
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASERX2;
|
| - rf(BULLET_WIDTH, BULLET_HEIGHT);
|
| - ctx.fill();
|
| - ctx.translate(8, 0);
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASERX2_DARK;
|
| - rf(BULLET_WIDTH-1, BULLET_HEIGHT-1);
|
| - ctx.fill();
|
| - ctx.shadowColor = ctx.fillStyle = Colors.GREEN_LASERX2;
|
| - rf(BULLET_WIDTH, BULLET_HEIGHT);
|
| - ctx.fill();
|
| - ctx.restore();
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - return img;
|
| - }, "bulletx2");
|
| -
|
| - // player bomb weapon
|
| - addRenderer((CanvasElement buffer) {
|
| - // NOTE: keep in sync with Asteroids.Bomb
|
| - var BOMB_RADIUS = 4;
|
| - buffer.width = buffer.height = BOMB_RADIUS*2 + GLOWSHADOWBLUR*2;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| -
|
| - var rf = () {
|
| - ctx.beginPath();
|
| - ctx.moveTo(BOMB_RADIUS * 2, 0);
|
| - for (var i = 0; i < 15; i++) {
|
| - ctx.rotate(PIO8);
|
| - if (i % 2 == 0) {
|
| - ctx.lineTo((BOMB_RADIUS * 2 / 0.525731) * 0.200811, 0);
|
| - } else {
|
| - ctx.lineTo(BOMB_RADIUS * 2, 0);
|
| - }
|
| - }
|
| - ctx.closePath();
|
| - };
|
| -
|
| - ctx.shadowBlur = GLOWSHADOWBLUR;
|
| - ctx.shadowColor = ctx.fillStyle = Colors.PLAYER_BOMB;
|
| - ctx.translate(buffer.width * 0.5, buffer.height * 0.5);
|
| - rf();
|
| - ctx.fill();
|
| -
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - return img;
|
| - }, "bomb");
|
| -
|
| - //enemy weapon
|
| - addRenderer((CanvasElement buffer) {
|
| - // NOTE: keep in sync with Asteroids.EnemyBullet
|
| - var BULLET_RADIUS = 4;
|
| - var imgs = [];
|
| - buffer.width = buffer.height = BULLET_RADIUS*2 + GLOWSHADOWBLUR*2;
|
| - CanvasRenderingContext2D ctx = buffer.getContext('2d');
|
| -
|
| - var rf = () {
|
| - ctx.beginPath();
|
| - ctx.moveTo(BULLET_RADIUS * 2, 0);
|
| - for (var i=0; i<7; i++) {
|
| - ctx.rotate(PIO4);
|
| - if (i % 2 == 0) {
|
| - ctx.lineTo((BULLET_RADIUS * 2/0.525731) * 0.200811, 0);
|
| - } else {
|
| - ctx.lineTo(BULLET_RADIUS * 2, 0);
|
| - }
|
| - }
|
| - ctx.closePath();
|
| - };
|
| -
|
| - ctx.shadowBlur = GLOWSHADOWBLUR;
|
| - ctx.shadowColor = ctx.fillStyle = Colors.ENEMY_SHIP;
|
| - ctx.translate(buffer.width * 0.5, buffer.height * 0.5);
|
| - ctx.beginPath();
|
| - ctx.arc(0, 0, BULLET_RADIUS-1, 0, TWOPI, true);
|
| - ctx.closePath();
|
| - ctx.fill();
|
| - rf();
|
| - ctx.fill();
|
| -
|
| - var img = new ImageElement();
|
| - img.src = buffer.toDataUrl("image/png");
|
| - return img;
|
| - }, "enemybullet");
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Game scene base class.
|
| - */
|
| -class Scene {
|
| - bool playable;
|
| - Interval interval;
|
| -
|
| - Scene([this.playable = true, this.interval = null]);
|
| -
|
| - /** Return true if this scene should update the actor list. */
|
| - bool isPlayable() => playable;
|
| -
|
| - void onInitScene() {
|
| - if (interval != null) {
|
| - // reset interval flag
|
| - interval.reset();
|
| - }
|
| - }
|
| -
|
| - void onBeforeRenderScene() {}
|
| - void onRenderScene(ctx) {}
|
| - void onRenderInterval(ctx) {}
|
| - void onMouseDownHandler(e) {}
|
| - void onMouseUpHandler(e) {}
|
| - void onKeyDownHandler(int keyCode) {}
|
| - void onKeyUpHandler(int keyCode) {}
|
| - bool isComplete() => false;
|
| -
|
| - bool onAccelerometer(double x, double y, double z) {
|
| - return true;
|
| - }
|
| -}
|
| -
|
| -class SoundManager {
|
| - bool _isDesktopEmulator;
|
| - Map _sounds = {};
|
| -
|
| - SoundManager(this._isDesktopEmulator);
|
| -
|
| - void createSound(Map props) {
|
| - if (!_isDesktopEmulator) {
|
| - var a = new AudioElement();
|
| - a.volume = props['volume'] / 100.0;;
|
| - a.src = props['url'];
|
| - _sounds[props['id']] = a;
|
| - }
|
| - }
|
| -
|
| - void play(String id) {
|
| - if (!_isDesktopEmulator) {
|
| - _sounds[id].play();
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * An actor that can be rendered by a bitmap. The sprite handling code deals
|
| - * with the increment of the current frame within the supplied bitmap sprite
|
| - * strip image, based on animation direction, animation speed and the animation
|
| - * length before looping. Call renderSprite() each frame.
|
| - *
|
| - * NOTE: by default sprites source images are 64px wide 64px by N frames high
|
| - * and scaled to the appropriate final size. Any other size input source should
|
| - * be set in the constructor.
|
| - */
|
| -class SpriteActor extends Actor {
|
| - SpriteActor(Vector position, Vector vector, [this.frameSize = 64])
|
| - : super(position, vector);
|
| -
|
| - /** Size in pixels of the width/height of an individual frame in the image. */
|
| - int frameSize;
|
| -
|
| - /**
|
| - * Animation image sprite reference.
|
| - * Sprite image sources are all currently 64px wide 64px by N frames high.
|
| - */
|
| - ImageElement animImage = null;
|
| -
|
| - /** Length in frames of the sprite animation. */
|
| - int animLength = 0;
|
| -
|
| - /** Animation direction, true for forward, false for reverse. */
|
| - bool animForward = true;
|
| -
|
| - /** Animation frame inc/dec speed. */
|
| - double animSpeed = 1.0;
|
| -
|
| - /** Current animation frame index. */
|
| - int animFrame = 0;
|
| -
|
| - /**
|
| - * Render sprite graphic based on current anim image, frame and anim direction
|
| - * Automatically updates the current anim frame.
|
| - */
|
| - void renderSprite(CanvasRenderingContext2D ctx, num x, num y, num s) {
|
| - renderImage(ctx, animImage, 0, animFrame << 6, frameSize, x, y, s);
|
| -
|
| - // update animation frame index
|
| - if (animForward) {
|
| - animFrame += (animSpeed * frameMultiplier).toInt();
|
| - if (animFrame >= animLength) {
|
| - animFrame = 0;
|
| - }
|
| - } else {
|
| - animFrame -= (animSpeed * frameMultiplier).toInt();
|
| - if (animFrame < 0) {
|
| - animFrame = animLength - 1;
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class Star {
|
| - Star();
|
| -
|
| - double MAXZ = 12.0;
|
| - double VELOCITY = 0.85;
|
| -
|
| - num x = 0;
|
| - num y = 0;
|
| - num z = 0;
|
| - num prevx = 0;
|
| - num prevy = 0;
|
| -
|
| - void init() {
|
| - // select a random point for the initial location
|
| - prevx = prevy = 0;
|
| - x = (random() * GameHandler.width - (GameHandler.width * 0.5)) * MAXZ;
|
| - y = (random() * GameHandler.height - (GameHandler.height * 0.5)) * MAXZ;
|
| - z = MAXZ;
|
| - }
|
| -
|
| - void render(CanvasRenderingContext2D ctx) {
|
| - var xx = x / z;
|
| - var yy = y / z;
|
| -
|
| - if (prevx != 0) {
|
| - ctx.lineWidth = 1.0 / z * 5 + 1;
|
| - ctx.beginPath();
|
| - ctx.moveTo(prevx + (GameHandler.width * 0.5),
|
| - prevy + (GameHandler.height * 0.5));
|
| - ctx.lineTo(xx + (GameHandler.width * 0.5),
|
| - yy + (GameHandler.height * 0.5));
|
| - ctx.stroke();
|
| - }
|
| -
|
| - prevx = xx;
|
| - prevy = yy;
|
| - }
|
| -}
|
| -
|
| -void drawText(CanvasRenderingContext2D g,
|
| - String txt, String font, num x, num y,
|
| - [String color]) {
|
| - g.save();
|
| - if (color != null) g.strokeStyle = color;
|
| - g.font = font;
|
| - g.strokeText(txt, x, y);
|
| - g.restore();
|
| -}
|
| -
|
| -void centerDrawText(CanvasRenderingContext2D g, String txt, String font, num y,
|
| - [String color]) {
|
| - g.save();
|
| - if (color != null) g.strokeStyle = color;
|
| - g.font = font;
|
| - g.strokeText(txt, (GameHandler.width - g.measureText(txt).width) / 2, y);
|
| - g.restore();
|
| -}
|
| -
|
| -void fillText(CanvasRenderingContext2D g, String txt, String font, num x, num y,
|
| - [String color]) {
|
| - g.save();
|
| - if (color != null) g.fillStyle = color;
|
| - g.font = font;
|
| - g.fillText(txt, x, y);
|
| - g.restore();
|
| -}
|
| -
|
| -void centerFillText(CanvasRenderingContext2D g, String txt, String font, num y,
|
| - [String color]) {
|
| - g.save();
|
| - if (color != null) g.fillStyle = color;
|
| - g.font = font;
|
| - g.fillText(txt, (GameHandler.width - g.measureText(txt).width) / 2, y);
|
| - g.restore();
|
| -}
|
| -
|
| -void drawScaledImage(CanvasRenderingContext2D ctx, ImageElement image,
|
| - num nx, num ny, num ns, num x, num y, num s) {
|
| - ctx.drawImageToRect(image, new Rect(x, y, s, s),
|
| - sourceRect: new Rect(nx, ny, ns, ns));
|
| -}
|
| -/**
|
| - * This method will automatically correct for objects moving on/off
|
| - * a cyclic canvas play area - if so it will render the appropriate stencil
|
| - * sections of the sprite top/bottom/left/right as needed to complete the image.
|
| - * Note that this feature can only be used if the sprite is absolutely
|
| - * positioned and not translated/rotated into position by canvas operations.
|
| - */
|
| -void renderImage(CanvasRenderingContext2D ctx, ImageElement image,
|
| - num nx, num ny, num ns, num x, num y, num s) {
|
| - print("renderImage(_,$nx,$ny,$ns,$ns,$x,$y,$s,$s)");
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns, x, y, s, s);
|
| -
|
| - if (x < 0) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - GameHandler.width + x, y, s, s);
|
| - }
|
| - if (y < 0) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - x, GameHandler.height + y, s, s);
|
| - }
|
| - if (x < 0 && y < 0) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - GameHandler.width + x, GameHandler.height + y, s, s);
|
| - }
|
| - if (x + s > GameHandler.width) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - x - GameHandler.width, y, s, s);
|
| - }
|
| - if (y + s > GameHandler.height) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - x, y - GameHandler.height, s, s);
|
| - }
|
| - if (x + s > GameHandler.width && y + s > GameHandler.height) {
|
| - ctx.drawImageScaledFromSource(image, nx, ny, ns, ns,
|
| - x - GameHandler.width, y - GameHandler.height, s, s);
|
| - }
|
| -}
|
| -
|
| -void renderImageRotated(CanvasRenderingContext2D ctx, ImageElement image,
|
| - num x, num y, num w, num h, num r) {
|
| - var w2 = w*0.5, h2 = h*0.5;
|
| - var rf = (tx, ty) {
|
| - ctx.save();
|
| - ctx.translate(tx, ty);
|
| - ctx.rotate(r);
|
| - ctx.drawImage(image, -w2, -h2);
|
| - ctx.restore();
|
| - };
|
| -
|
| - rf(x, y);
|
| -
|
| - if (x - w2 < 0) {
|
| - rf(GameHandler.width + x, y);
|
| - }
|
| - if (y - h2 < 0) {
|
| - rf(x, GameHandler.height + y);
|
| - }
|
| - if (x - w2 < 0 && y - h2 < 0) {
|
| - rf(GameHandler.width + x, GameHandler.height + y);
|
| - }
|
| - if (x - w2 + w > GameHandler.width) {
|
| - rf(x - GameHandler.width, y);
|
| - }
|
| - if (y - h2 + h > GameHandler.height){
|
| - rf(x, y - GameHandler.height);
|
| - }
|
| - if (x - w2 + w > GameHandler.width && y - h2 + h > GameHandler.height) {
|
| - rf(x - GameHandler.width, y - GameHandler.height);
|
| - }
|
| -}
|
| -
|
| -void renderImageRotated2(CanvasRenderingContext2D ctx, ImageElement image,
|
| - num x, num y, num w, num h, num r) {
|
| - print("Rendering rotated sprite ${image.src} to dest $x,$y");
|
| - var w2 = w*0.5, h2 = h*0.5;
|
| - var rf = (tx, ty) {
|
| - ctx.save();
|
| - ctx.translate(tx, ty);
|
| - ctx.rotate(r);
|
| - ctx.drawImage(image, -w2, -h2);
|
| - ctx.restore();
|
| - };
|
| -
|
| - rf(x, y);
|
| -
|
| - if (x - w2 < 0) {
|
| - rf(GameHandler.width + x, y);
|
| - }
|
| - if (y - h2 < 0) {
|
| - rf(x, GameHandler.height + y);
|
| - }
|
| - if (x - w2 < 0 && y - h2 < 0) {
|
| - rf(GameHandler.width + x, GameHandler.height + y);
|
| - }
|
| - if (x - w2 + w > GameHandler.width) {
|
| - rf(x - GameHandler.width, y);
|
| - }
|
| - if (y - h2 + h > GameHandler.height){
|
| - rf(x, y - GameHandler.height);
|
| - }
|
| - if (x - w2 + w > GameHandler.width && y - h2 + h > GameHandler.height) {
|
| - rf(x - GameHandler.width, y - GameHandler.height);
|
| - }
|
| -}
|
| -
|
| -class Vector {
|
| - num x, y;
|
| -
|
| - Vector(this.x, this.y);
|
| -
|
| - Vector clone() => new Vector(x, y);
|
| -
|
| - void set(Vector v) {
|
| - x = v.x;
|
| - y = v.y;
|
| - }
|
| -
|
| - Vector add(Vector v) {
|
| - x += v.x;
|
| - y += v.y;
|
| - return this;
|
| - }
|
| -
|
| - Vector nadd(Vector v) => new Vector(x + v.x, y + v.y);
|
| -
|
| - Vector sub(Vector v) {
|
| - x -= v.x;
|
| - y -= v.y;
|
| - return this;
|
| - }
|
| -
|
| - Vector nsub(Vector v) => new Vector(x - v.x, y - v.y);
|
| -
|
| - double dot(Vector v) => x * v.x + y * v.y;
|
| -
|
| - double length() => Math.sqrt(x * x + y * y);
|
| -
|
| - double distance(Vector v) {
|
| - var dx = x - v.x;
|
| - var dy = y - v.y;
|
| - return Math.sqrt(dx * dx + dy * dy);
|
| - }
|
| -
|
| - double theta() => Math.atan2(y, x);
|
| -
|
| - double thetaTo(Vector vec) {
|
| - // calc angle between the two vectors
|
| - var v = clone().norm();
|
| - var w = vec.clone().norm();
|
| - return Math.sqrt(v.dot(w));
|
| - }
|
| -
|
| - double thetaTo2(Vector vec) =>
|
| - Math.atan2(vec.y, vec.x) - Math.atan2(y, x);
|
| -
|
| - Vector norm() {
|
| - var len = length();
|
| - x /= len;
|
| - y /= len;
|
| - return this;
|
| - }
|
| -
|
| - Vector nnorm() {
|
| - var len = length();
|
| - return new Vector(x / len, y / len);
|
| - }
|
| -
|
| - rotate(num a) {
|
| - var ca = Math.cos(a);
|
| - var sa = Math.sin(a);
|
| - var newx = x*ca - y*sa;
|
| - var newy = x*sa + y*ca;
|
| - x = newx;
|
| - y = newy;
|
| - return this;
|
| - }
|
| -
|
| - Vector nrotate(num a) {
|
| - var ca = Math.cos(a);
|
| - var sa = Math.sin(a);
|
| - return new Vector(x * ca - y * sa, x * sa + y * ca);
|
| - }
|
| -
|
| - Vector invert() {
|
| - x = -x;
|
| - y = -y;
|
| - return this;
|
| - }
|
| -
|
| - Vector ninvert() {
|
| - return new Vector(-x, -y);
|
| - }
|
| -
|
| - Vector scale(num s) {
|
| - x *= s;
|
| - y *= s;
|
| - return this;
|
| - }
|
| -
|
| - Vector nscale(num s) {
|
| - return new Vector(x * s, y * s);
|
| - }
|
| -
|
| - Vector scaleTo(num s) {
|
| - var len = s / length();
|
| - x *= len;
|
| - y *= len;
|
| - return this;
|
| - }
|
| -
|
| - nscaleTo(num s) {
|
| - var len = s / length();
|
| - return new Vector(x * len, y * len);
|
| - }
|
| -
|
| - trim(num minx, num maxx, num miny, num maxy) {
|
| - if (x < minx) x = minx;
|
| - else if (x > maxx) x = maxx;
|
| - if (y < miny) y = miny;
|
| - else if (y > maxy) y = maxy;
|
| - }
|
| -
|
| - wrap(num minx, num maxx, num miny, num maxy) {
|
| - if (x < minx) x = maxx;
|
| - else if (x > maxx) x = minx;
|
| - if (y < miny) y = maxy;
|
| - else if (y > maxy) y = miny;
|
| - }
|
| -
|
| - String toString() => "<$x, $y>";
|
| -}
|
| -
|
| -class Weapon {
|
| - Weapon(this.player, [this.rechargeTime = 125]);
|
| -
|
| - int rechargeTime;
|
| - int lastFired = 0;
|
| - Player player;
|
| -
|
| - bool canFire() =>
|
| - (GameHandler.frameStart - lastFired) >= rechargeTime;
|
| -
|
| - List fire() {
|
| - if (canFire()) {
|
| - lastFired = GameHandler.frameStart;
|
| - return doFire();
|
| - }
|
| - }
|
| -
|
| - Bullet makeBullet(double headingDelta, double vectorY,
|
| - [int lifespan = 1300]) {
|
| - var h = player.heading - headingDelta;
|
| - var t = new Vector(0.0, vectorY).rotate(h * RAD).add(player.velocity);
|
| - return new Bullet(player.position.clone(), t, h, lifespan);
|
| - }
|
| -
|
| - List doFire() => [];
|
| -}
|
| -
|
| -class PrimaryWeapon extends Weapon {
|
| - PrimaryWeapon(Player player) : super(player);
|
| -
|
| - List doFire() => [ makeBullet(0.0, -4.5) ];
|
| -}
|
| -
|
| -class TwinCannonsWeapon extends Weapon {
|
| - TwinCannonsWeapon(Player player) : super(player, 150);
|
| -
|
| - List doFire() {
|
| - var h = player.heading;
|
| - var t = new Vector(0.0, -4.5).rotate(h * RAD).add(player.velocity);
|
| - return [ new BulletX2(player.position.clone(), t, h) ];
|
| - }
|
| -}
|
| -
|
| -class VSprayCannonsWeapon extends Weapon {
|
| - VSprayCannonsWeapon(Player player) : super(player, 250);
|
| -
|
| - List doFire() =>
|
| - [ makeBullet(-15.0, -3.75),
|
| - makeBullet(0.0, -3.75),
|
| - makeBullet(15.0, -3.75) ];
|
| -}
|
| -
|
| -class SideGunWeapon extends Weapon {
|
| - SideGunWeapon(Player player) : super(player, 250);
|
| -
|
| - List doFire() =>
|
| - [ makeBullet(-90.0, -4.5, 750),
|
| - makeBullet(+90.0, -4.5, 750)];
|
| -}
|
| -
|
| -class RearGunWeapon extends Weapon {
|
| - RearGunWeapon(Player player) : super(player, 250);
|
| -
|
| - List doFire() => [makeBullet(180.0, -4.5, 750)];
|
| -}
|
| -
|
| -class Input {
|
| - bool left, right, thrust, shield, fireA, fireB;
|
| -
|
| - Input() { reset(); }
|
| -
|
| - void reset() {
|
| - left = right = thrust = shield = fireA = fireB = false;
|
| - }
|
| -}
|
| -
|
| -void resize(int w, int h) {}
|
| -
|
| -
|
| -void setup(canvasp, int w, int h, int f) {
|
| - var canvas;
|
| - if (canvasp == null) {
|
| - log("Allocating canvas");
|
| - canvas = new CanvasElement(width: w, height: h);
|
| - document.body.nodes.add(canvas);
|
| - } else {
|
| - log("Using parent canvas");
|
| - canvas = canvasp;
|
| - }
|
| -
|
| - for (var i = 0; i < 4; i++) {
|
| - _asteroidImgs.add(new ImageElement());
|
| - }
|
| - // attach to the image onload handler
|
| - // once the background is loaded, we can boot up the game
|
| - _backgroundImg.onLoad.listen((e) {
|
| - // init our game with Game.Main derived instance
|
| - log("Loaded background image ${_backgroundImg.src}");
|
| - GameHandler.init(canvas);
|
| - GameHandler.start(new AsteroidsMain());
|
| - });
|
| - _backgroundImg.src = 'bg3_1.png';
|
| - loadSounds(f == 1);
|
| -}
|
| -
|
| -void loadSounds(bool isDesktopEmulator) {
|
| - soundManager = new SoundManager(isDesktopEmulator);
|
| - // load game sounds
|
| - soundManager.createSound({
|
| - 'id': 'laser',
|
| - 'url': 'laser.$sfx_extension',
|
| - 'volume': 40,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'enemy_bomb',
|
| - 'url': 'enemybomb.$sfx_extension',
|
| - 'volume': 60,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'big_boom',
|
| - 'url': 'bigboom.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'asteroid_boom1',
|
| - 'url': 'explosion1.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'asteroid_boom2',
|
| - 'url': 'explosion2.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'asteroid_boom3',
|
| - 'url': 'explosion3.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'asteroid_boom4',
|
| - 'url': 'explosion4.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| - soundManager.createSound({
|
| - 'id': 'powerup',
|
| - 'url': 'powerup.$sfx_extension',
|
| - 'volume': 50,
|
| - 'autoLoad': true,
|
| - 'multiShot': true
|
| - });
|
| -}
|
| -
|
|
|