|  | part of game; | 
|  |  | 
|  | abstract class GameObject extends Node { | 
|  | GameObject(this.f); | 
|  |  | 
|  | double radius = 0.0; | 
|  | double removeLimit = 1280.0; | 
|  | bool canDamageShip = true; | 
|  | bool canBeDamaged = true; | 
|  | bool canBeCollected = false; | 
|  | double maxDamage = 3.0; | 
|  | double damage = 0.0; | 
|  |  | 
|  | final GameObjectFactory f; | 
|  |  | 
|  | Paint _paintDebug = new Paint() | 
|  | ..color=new Color(0xffff0000) | 
|  | ..strokeWidth = 1.0 | 
|  | ..setStyle(sky.PaintingStyle.stroke); | 
|  |  | 
|  | bool collidingWith(GameObject obj) { | 
|  | return (GameMath.pointQuickDist(position, obj.position) | 
|  | < radius + obj.radius); | 
|  | } | 
|  |  | 
|  | void move() { | 
|  | } | 
|  |  | 
|  | void removeIfOffscreen(double scroll) { | 
|  | ; | 
|  | if (-position.y > scroll + removeLimit || | 
|  | -position.y < scroll - 50.0) { | 
|  | removeFromParent(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void destroy() { | 
|  | if (parent != null) { | 
|  | Explosion explo = createExplosion(); | 
|  | if (explo != null) { | 
|  | explo.position = position; | 
|  | parent.addChild(explo); | 
|  | } | 
|  |  | 
|  | Collectable powerUp = createPowerUp(); | 
|  | if (powerUp != null) { | 
|  | f.addGameObject(powerUp, position); | 
|  | } | 
|  |  | 
|  | removeFromParent(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void collect() { | 
|  | removeFromParent(); | 
|  | } | 
|  |  | 
|  | void addDamage(double d) { | 
|  | if (!canBeDamaged) return; | 
|  |  | 
|  | damage += d; | 
|  | if (damage >= maxDamage) { | 
|  | destroy(); | 
|  | f.playerState.score += (maxDamage * 10).ceil(); | 
|  | } | 
|  | } | 
|  |  | 
|  | Explosion createExplosion() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | Collectable createPowerUp() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | void paint(PaintingCanvas canvas) { | 
|  | if (_drawDebug) { | 
|  | canvas.drawCircle(Point.origin, radius, _paintDebug); | 
|  | } | 
|  | super.paint(canvas); | 
|  | } | 
|  |  | 
|  | void setupActions() { | 
|  | } | 
|  | } | 
|  |  | 
|  | class LevelLabel extends GameObject { | 
|  | LevelLabel(GameObjectFactory f, int level) : super(f) { | 
|  | canDamageShip = false; | 
|  | canBeDamaged = false; | 
|  |  | 
|  | Label lbl = new Label( | 
|  | "L E V E L $level", | 
|  | new TextStyle( | 
|  | textAlign: TextAlign.center, | 
|  | color:new Color(0xffffffff), | 
|  | fontSize: 24.0, | 
|  | fontWeight: FontWeight.w600 | 
|  | )); | 
|  | addChild(lbl); | 
|  | } | 
|  | } | 
|  |  | 
|  | class Ship extends GameObject { | 
|  | Ship(GameObjectFactory f) : super(f) { | 
|  | // Add main ship sprite | 
|  | _sprt = new Sprite(f.sheet["ship.png"]); | 
|  | _sprt.scale = 0.3; | 
|  | _sprt.rotation = -90.0; | 
|  | addChild(_sprt); | 
|  |  | 
|  | _sprtShield = new Sprite(f.sheet["shield.png"]); | 
|  | _sprtShield.scale = 0.35; | 
|  | _sprtShield.transferMode = sky.TransferMode.plus; | 
|  | addChild(_sprtShield); | 
|  |  | 
|  | radius = 20.0; | 
|  | canBeDamaged = false; | 
|  | canDamageShip = false; | 
|  |  | 
|  | // Set start position | 
|  | position = new Point(0.0, 50.0); | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | Sprite _sprtShield; | 
|  |  | 
|  | void applyThrust(Point joystickValue, double scroll) { | 
|  | Point oldPos = position; | 
|  | Point target = new Point(joystickValue.x * 160.0, joystickValue.y * 220.0 - 250.0 - scroll); | 
|  | double filterFactor = 0.2; | 
|  |  | 
|  | position = new Point( | 
|  | GameMath.filter(oldPos.x, target.x, filterFactor), | 
|  | GameMath.filter(oldPos.y, target.y, filterFactor)); | 
|  | } | 
|  |  | 
|  | void setupActions() { | 
|  | ActionTween rotate = new ActionTween((a) => _sprtShield.rotation = a, 0.0, 360.0, 1.0); | 
|  | _sprtShield.actions.run(new ActionRepeatForever(rotate)); | 
|  | } | 
|  |  | 
|  | void update(double dt) { | 
|  | // Update shield | 
|  | if (f.playerState.shieldActive) { | 
|  | if (f.playerState.shieldDeactivating) | 
|  | _sprtShield.visible = !_sprtShield.visible; | 
|  | else | 
|  | _sprtShield.visible = true; | 
|  | } else { | 
|  | _sprtShield.visible = false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Laser extends GameObject { | 
|  | double impact = 0.0; | 
|  |  | 
|  | final List<Color> laserColors = [ | 
|  | new Color(0xff95f4fb), | 
|  | new Color(0xff5bff35), | 
|  | new Color(0xffff886c), | 
|  | new Color(0xffffd012), | 
|  | new Color(0xfffd7fff) | 
|  | ]; | 
|  |  | 
|  | Laser(GameObjectFactory f, int level, double r) : super(f) { | 
|  | // Game object properties | 
|  | radius = 10.0; | 
|  | removeLimit = _gameSizeHeight + radius; | 
|  | canDamageShip = false; | 
|  | canBeDamaged = false; | 
|  | impact = 1.0 + level * 0.5; | 
|  |  | 
|  | // Offset for movement | 
|  | _offset = new Offset( | 
|  | math.cos(radians(r)) * 8.0, | 
|  | math.sin(radians(r)) * 8.0 - f.playerState.scrollSpeed); | 
|  |  | 
|  | // Drawing properties | 
|  | rotation = r + 90.0; | 
|  | int numLasers = level % 3 + 1; | 
|  | Color laserColor = laserColors[(level ~/ 3) % laserColors.length]; | 
|  |  | 
|  | // Add sprites | 
|  | List<Sprite> sprites = []; | 
|  | for (int i = 0; i < numLasers; i++) { | 
|  | Sprite sprt = new Sprite(f.sheet["explosion_particle.png"]); | 
|  | sprt.scale = 0.5; | 
|  | sprt.colorOverlay = laserColor; | 
|  | sprt.transferMode = sky.TransferMode.plus; | 
|  | addChild(sprt); | 
|  | sprites.add(sprt); | 
|  | } | 
|  |  | 
|  | // Position the individual sprites | 
|  | if (numLasers == 2) { | 
|  | sprites[0].position = new Point(-3.0, 0.0); | 
|  | sprites[1].position = new Point(3.0, 0.0); | 
|  | } else if (numLasers == 3) { | 
|  | sprites[0].position = new Point(-4.0, 0.0); | 
|  | sprites[1].position = new Point(4.0, 0.0); | 
|  | sprites[2].position = new Point(0.0, -2.0); | 
|  | } | 
|  | } | 
|  |  | 
|  | Offset _offset; | 
|  |  | 
|  | void move() { | 
|  | position += _offset; | 
|  | } | 
|  |  | 
|  | Explosion createExplosion() { | 
|  | return new ExplosionMini(f.sheet); | 
|  | } | 
|  | } | 
|  |  | 
|  | Color colorForDamage(double damage, double maxDamage) { | 
|  | int alpha = ((200.0 * damage) ~/ maxDamage).clamp(0, 200); | 
|  | return new Color.fromARGB(alpha, 255, 3, 86); | 
|  | } | 
|  |  | 
|  | abstract class Obstacle extends GameObject { | 
|  |  | 
|  | Obstacle(GameObjectFactory f) : super(f); | 
|  |  | 
|  | double explosionScale = 1.0; | 
|  |  | 
|  | Explosion createExplosion() { | 
|  | SoundEffectPlayer.sharedInstance().play(f.sounds["explosion"]); | 
|  | Explosion explo = new ExplosionBig(f.sheet); | 
|  | explo.scale = explosionScale; | 
|  | return explo; | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract class Asteroid extends Obstacle { | 
|  | Asteroid(GameObjectFactory f) : super(f); | 
|  |  | 
|  | Sprite _sprt; | 
|  |  | 
|  | void setupActions() { | 
|  | // Rotate obstacle | 
|  | int direction = 1; | 
|  | if (randomBool()) direction = -1; | 
|  | ActionTween rotate = new ActionTween( | 
|  | (a) => _sprt.rotation = a, | 
|  | 0.0, 360.0 * direction, 5.0 + 5.0 * randomDouble()); | 
|  | _sprt.actions.run(new ActionRepeatForever(rotate)); | 
|  | } | 
|  |  | 
|  | set damage(double d) { | 
|  | super.damage = d; | 
|  | _sprt.colorOverlay = colorForDamage(d, maxDamage); | 
|  | } | 
|  |  | 
|  | Collectable createPowerUp() { | 
|  | return new Coin(f); | 
|  | } | 
|  | } | 
|  |  | 
|  | class AsteroidBig extends Asteroid { | 
|  | AsteroidBig(GameObjectFactory f) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["asteroid_big_${randomInt(3)}.png"]); | 
|  | _sprt.scale = 0.3; | 
|  | radius = 25.0; | 
|  | maxDamage = 5.0; | 
|  | addChild(_sprt); | 
|  | } | 
|  | } | 
|  |  | 
|  | class AsteroidSmall extends Asteroid { | 
|  | AsteroidSmall(GameObjectFactory f) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["asteroid_small_${randomInt(3)}.png"]); | 
|  | _sprt.scale = 0.3; | 
|  | radius = 12.0; | 
|  | maxDamage = 3.0; | 
|  | addChild(_sprt); | 
|  | } | 
|  | } | 
|  |  | 
|  | class AsteroidPowerUp extends AsteroidBig { | 
|  | AsteroidPowerUp(GameObjectFactory f) : super(f); | 
|  |  | 
|  | Collectable createPowerUp() { | 
|  | return new PowerUp(f, nextPowerUpType()); | 
|  | } | 
|  | } | 
|  |  | 
|  | class EnemyScout extends Obstacle { | 
|  | EnemyScout(GameObjectFactory f) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["enemy_scout_0.png"]); | 
|  | _sprt.scale = 0.32; | 
|  | radius = 12.0; | 
|  | maxDamage = 1.0; | 
|  | addChild(_sprt); | 
|  |  | 
|  | constraints = [new ConstraintRotationToMovement(dampening: 0.5)]; | 
|  | } | 
|  |  | 
|  | final double _swirlSpacing = 80.0; | 
|  |  | 
|  | _addRandomSquare(List<Offset> offsets, double x, double y) { | 
|  | double xMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing; | 
|  | double yMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing; | 
|  |  | 
|  | if (randomBool()) { | 
|  | offsets.addAll([ | 
|  | new Offset(x, y), | 
|  | new Offset(xMove + x, y), | 
|  | new Offset(xMove + x, yMove + y), | 
|  | new Offset(x, yMove + y), | 
|  | new Offset(x, y) | 
|  | ]); | 
|  | } else { | 
|  | offsets.addAll([ | 
|  | new Offset(x, y), | 
|  | new Offset(x, y + yMove), | 
|  | new Offset(xMove + x, yMove + y), | 
|  | new Offset(xMove + x, y), | 
|  | new Offset(x, y) | 
|  | ]); | 
|  | } | 
|  | } | 
|  |  | 
|  | void setupActions() { | 
|  |  | 
|  | List<Offset> offsets = []; | 
|  | _addRandomSquare(offsets, -_swirlSpacing, 0.0); | 
|  | _addRandomSquare(offsets, _swirlSpacing, 0.0); | 
|  | offsets.add(new Offset(-_swirlSpacing, 0.0)); | 
|  |  | 
|  | List<Point> points = []; | 
|  | for (Offset offset in offsets) { | 
|  | points.add(position + offset); | 
|  | } | 
|  |  | 
|  | ActionSpline spline = new ActionSpline((a) => position = a, points, 6.0); | 
|  | spline.tension = 0.7; | 
|  | actions.run(new ActionRepeatForever(spline)); | 
|  | } | 
|  |  | 
|  | Collectable createPowerUp() { | 
|  | return new Coin(f); | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | } | 
|  |  | 
|  | class EnemyDestroyer extends Obstacle { | 
|  | EnemyDestroyer(GameObjectFactory f) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["enemy_destroyer_1.png"]); | 
|  | _sprt.scale = 0.32; | 
|  | radius = 24.0; | 
|  | maxDamage = 4.0; | 
|  | addChild(_sprt); | 
|  |  | 
|  | constraints = [new ConstraintRotationToNode(f.level.ship, dampening: 0.05)]; | 
|  | } | 
|  |  | 
|  | int _countDown = randomInt(120) + 240; | 
|  |  | 
|  | void setupActions() { | 
|  | ActionCircularMove circle = new ActionCircularMove( | 
|  | (a) => position = a, | 
|  | position, 40.0, | 
|  | 360.0 * randomDouble(), | 
|  | randomBool(), | 
|  | 3.0); | 
|  | actions.run(new ActionRepeatForever(circle)); | 
|  | } | 
|  |  | 
|  | Collectable createPowerUp() { | 
|  | return new Coin(f); | 
|  | } | 
|  |  | 
|  | void update(double dt) { | 
|  | _countDown -= 1; | 
|  | if (_countDown <= 0) { | 
|  | // Shoot at player | 
|  | EnemyLaser laser = new EnemyLaser(f, rotation, 5.0, new Color(0xffffe38e)); | 
|  | laser.position = position; | 
|  | f.level.addChild(laser); | 
|  |  | 
|  | _countDown = 60 + randomInt(120); | 
|  | } | 
|  | } | 
|  |  | 
|  | set damage(double d) { | 
|  | super.damage = d; | 
|  | _sprt.colorOverlay = colorForDamage(d, maxDamage); | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | } | 
|  |  | 
|  | class EnemyLaser extends Obstacle { | 
|  | EnemyLaser(GameObjectFactory f, double rotation, double speed, Color color) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["explosion_particle.png"]); | 
|  | _sprt.scale = 0.5; | 
|  | _sprt.rotation = rotation + 90; | 
|  | _sprt.colorOverlay = color; | 
|  | addChild(_sprt); | 
|  |  | 
|  | canDamageShip = true; | 
|  | canBeDamaged = false; | 
|  |  | 
|  | double rad = radians(rotation); | 
|  | _movement = new Offset(math.cos(rad) * speed, math.sin(rad) * speed); | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | Offset _movement; | 
|  |  | 
|  | void move() { | 
|  | position += _movement; | 
|  | } | 
|  | } | 
|  |  | 
|  | class EnemyBoss extends Obstacle { | 
|  | EnemyBoss(GameObjectFactory f) : super(f) { | 
|  | radius = 48.0; | 
|  | _sprt = new Sprite(f.sheet["enemy_destroyer_1.png"]); | 
|  | _sprt.scale = 0.64; | 
|  | addChild(_sprt); | 
|  | maxDamage = 40.0; | 
|  |  | 
|  | constraints = [new ConstraintRotationToNode(f.level.ship, dampening: 0.05)]; | 
|  |  | 
|  | _powerBar = new PowerBar(new Size(60.0, 10.0)); | 
|  | _powerBar.pivot = new Point(0.5, 0.5); | 
|  | f.level.addChild(_powerBar); | 
|  | _powerBar.constraints = [new ConstraintPositionToNode( | 
|  | this, | 
|  | dampening: 0.5, | 
|  | offset: new Offset(0.0, -70.0) | 
|  | )]; | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | PowerBar _powerBar; | 
|  |  | 
|  | int _countDown = randomInt(120) + 240; | 
|  |  | 
|  | void update(double dt) { | 
|  | _countDown -= 1; | 
|  | if (_countDown <= 0) { | 
|  | // Shoot at player | 
|  | fire(10.0); | 
|  | fire(0.0); | 
|  | fire(-10.0); | 
|  |  | 
|  | _countDown = 60 + randomInt(120); | 
|  | } | 
|  | } | 
|  |  | 
|  | void fire(double r) { | 
|  | r += rotation; | 
|  | EnemyLaser laser = new EnemyLaser(f, r, 5.0, new Color(0xffffe38e)); | 
|  |  | 
|  | double rad = radians(r); | 
|  | Offset startOffset = new Offset(math.cos(rad) * 30.0, math.sin(rad) * 30.0); | 
|  |  | 
|  | laser.position = position + startOffset; | 
|  | f.level.addChild(laser); | 
|  | } | 
|  |  | 
|  | void setupActions() { | 
|  | ActionOscillate oscillate = new ActionOscillate((a) => position = a, position, 120.0, 3.0); | 
|  | actions.run(new ActionRepeatForever(oscillate)); | 
|  | } | 
|  |  | 
|  | void destroy() { | 
|  | f.playerState.boss = null; | 
|  | _powerBar.removeFromParent(); | 
|  |  | 
|  | // Flash the screen | 
|  | NodeWithSize screen = f.playerState.parent; | 
|  | screen.addChild(new Flash(screen.size, 1.0)); | 
|  | super.destroy(); | 
|  |  | 
|  | // Add coins | 
|  | for (int i = 0; i < 20; i++) { | 
|  | Coin coin = new Coin(f); | 
|  | Point pos = new Point( | 
|  | randomSignedDouble() * 160, | 
|  | position.y + randomSignedDouble() * 160.0); | 
|  | f.addGameObject(coin, pos); | 
|  | } | 
|  | } | 
|  |  | 
|  | Explosion createExplosion() { | 
|  | ExplosionBig explo = new ExplosionBig(f.sheet); | 
|  | explo.scale = 1.5; | 
|  | return explo; | 
|  | } | 
|  |  | 
|  | set damage(double d) { | 
|  | super.damage = d; | 
|  | _sprt.actions.stopAll(); | 
|  | _sprt.actions.run(new ActionTween( | 
|  | (a) =>_sprt.colorOverlay = a, | 
|  | new Color.fromARGB(180, 255, 3, 86), | 
|  | new Color(0x00000000), | 
|  | 0.3 | 
|  | )); | 
|  |  | 
|  | _powerBar.power = (1.0 - (damage / maxDamage)).clamp(0.0, 1.0); | 
|  | } | 
|  | } | 
|  |  | 
|  | class Collectable extends GameObject { | 
|  | Collectable(GameObjectFactory f) : super(f) { | 
|  | canDamageShip = false; | 
|  | canBeDamaged = false; | 
|  | canBeCollected = true; | 
|  |  | 
|  | zPosition = 20.0; | 
|  | } | 
|  | } | 
|  |  | 
|  | class Coin extends Collectable { | 
|  | Coin(GameObjectFactory f) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["coin.png"]); | 
|  | _sprt.scale = 0.7; | 
|  | addChild(_sprt); | 
|  |  | 
|  | radius = 7.5; | 
|  | } | 
|  |  | 
|  | void setupActions() { | 
|  | // Rotate | 
|  | ActionTween rotate = new ActionTween((a) => _sprt.rotation = a, 0.0, 360.0, 1.0); | 
|  | actions.run(new ActionRepeatForever(rotate)); | 
|  |  | 
|  | // Fade in | 
|  | ActionTween fadeIn = new ActionTween((a) => _sprt.opacity = a, 0.0, 1.0, 0.6); | 
|  | actions.run(fadeIn); | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  |  | 
|  | void collect() { | 
|  | f.playerState.addCoin(this); | 
|  | super.collect(); | 
|  | } | 
|  | } | 
|  |  | 
|  | enum PowerUpType { | 
|  | shield, | 
|  | speedLaser, | 
|  | sideLaser, | 
|  | speedBoost, | 
|  | } | 
|  |  | 
|  | List<PowerUpType> _powerUpTypes = new List.from(PowerUpType.values); | 
|  | int _lastPowerUp = _powerUpTypes.length; | 
|  |  | 
|  | PowerUpType nextPowerUpType() { | 
|  | if (_lastPowerUp >= _powerUpTypes.length) { | 
|  | _powerUpTypes.shuffle(); | 
|  | _lastPowerUp = 0; | 
|  | } | 
|  |  | 
|  | PowerUpType type = _powerUpTypes[_lastPowerUp]; | 
|  | _lastPowerUp++; | 
|  |  | 
|  | return type; | 
|  | } | 
|  |  | 
|  | class PowerUp extends Collectable { | 
|  | PowerUp(GameObjectFactory f, this.type) : super(f) { | 
|  | _sprt = new Sprite(f.sheet["coin.png"]); | 
|  | _sprt.scale = 1.2; | 
|  | addChild(_sprt); | 
|  |  | 
|  | radius = 10.0; | 
|  | } | 
|  |  | 
|  | Sprite _sprt; | 
|  | PowerUpType type; | 
|  |  | 
|  | void setupActions() { | 
|  | ActionTween rotate = new ActionTween((a) => _sprt.rotation = a, 0.0, 360.0, 1.0); | 
|  | actions.run(new ActionRepeatForever(rotate)); | 
|  |  | 
|  | // Fade in | 
|  | ActionTween fadeIn = new ActionTween((a) => _sprt.opacity = a, 0.0, 1.0, 0.6); | 
|  | actions.run(fadeIn); | 
|  | } | 
|  |  | 
|  | void collect() { | 
|  | f.playerState.activatePowerUp(type); | 
|  | super.collect(); | 
|  | } | 
|  | } |