blob: c819ab58209e9409cd865d1fb4071eeb8f10c6ce [file] [log] [blame]
part of game;
final double _gameSizeWidth = 320.0;
double _gameSizeHeight = 320.0;
final bool _drawDebug = false;
class GameDemoNode extends NodeWithSize {
GameDemoNode(
this._images,
this._spritesGame,
this._spritesUI,
this._sounds,
this._gameOverCallback
): super(new Size(320.0, 320.0)) {
// Add background
_background = new RepeatedImage(_images["assets/starfield.png"]);
addChild(_background);
// Create starfield
_starField = new StarField(_spritesGame, 200);
addChild(_starField);
// Add nebula
_nebula = new RepeatedImage(_images["assets/nebula.png"], sky.TransferMode.plus);
addChild(_nebula);
// Setup game screen, it will always be anchored to the bottom of the screen
_gameScreen = new Node();
addChild(_gameScreen);
// Setup the level and add it to the screen, the level is the node where
// all our game objects live. It is moved to scroll the game
_level = new Level();
_gameScreen.addChild(_level);
_objectFactory = new GameObjectFactory(_spritesGame, _sounds, _level);
_level.ship = new Ship(_objectFactory);
_level.addChild(_level.ship);
// Add the joystick
_joystick = new VirtualJoystick();
_gameScreen.addChild(_joystick);
// Add HUD
_hud = new Hud(_spritesUI);
addChild(_hud);
// Add initial game objects
addObjects();
}
// Resources
ImageMap _images;
Map<String, SoundEffect> _sounds;
SpriteSheet _spritesGame;
SpriteSheet _spritesUI;
// Sounds
SoundEffectPlayer _effectPlayer = SoundEffectPlayer.sharedInstance();
// Callback
Function _gameOverCallback;
// Game screen nodes
Node _gameScreen;
VirtualJoystick _joystick;
GameObjectFactory _objectFactory;
Level _level;
StarField _starField;
RepeatedImage _background;
RepeatedImage _nebula;
Hud _hud;
// Game properties
double _scrollSpeed = 2.0;
double _scroll = 0.0;
int _framesToFire = 0;
int _framesBetweenShots = 20;
bool _gameOver = false;
void spriteBoxPerformedLayout() {
_gameSizeHeight = spriteBox.visibleArea.height;
_gameScreen.position = new Point(0.0, _gameSizeHeight);
}
void update(double dt) {
// Scroll the level
_scroll = _level.scroll(_scrollSpeed);
_starField.move(0.0, _scrollSpeed);
_background.move(_scrollSpeed * 0.1);
_nebula.move(_scrollSpeed);
// Add objects
addObjects();
// Move the ship
if (!_gameOver) {
_level.ship.applyThrust(_joystick.value, _scroll);
}
// Add shots
if (_framesToFire == 0 && _joystick.isDown && !_gameOver) {
fire();
_framesToFire = _framesBetweenShots;
}
if (_framesToFire > 0) _framesToFire--;
// Move game objects
for (Node node in _level.children) {
if (node is GameObject) {
node.move();
}
}
// Remove offscreen game objects
for (int i = _level.children.length - 1; i >= 0; i--) {
Node node = _level.children[i];
if (node is GameObject) {
node.removeIfOffscreen(_scroll);
}
}
if (_gameOver) return;
// Check for collisions between lasers and objects that can take damage
List<Laser> lasers = [];
for (Node node in _level.children) {
if (node is Laser) lasers.add(node);
}
List<GameObject> damageables = [];
for (Node node in _level.children) {
if (node is GameObject && node.canBeDamaged) damageables.add(node);
}
for (Laser laser in lasers) {
for (GameObject damageable in damageables) {
if (laser.collidingWith(damageable)) {
// Hit something that can take damage
_hud.score += damageable.addDamage(laser.impact);
laser.destroy();
}
}
}
// Check for collsions between ship and objects that can damage the ship
List<Node> nodes = new List.from(_level.children);
for (Node node in nodes) {
if (node is GameObject && node.canDamageShip) {
if (node.collidingWith(_level.ship)) {
// The ship was hit :(
killShip();
_level.ship.visible = false;
}
}
}
}
int _chunk = 0;
double _chunkSpacing = 640.0;
void addObjects() {
while (_scroll + _chunkSpacing >= _chunk * _chunkSpacing) {
addLevelChunk(
_chunk,
-_chunk * _chunkSpacing - _chunkSpacing);
_chunk += 1;
}
}
void addLevelChunk(int chunk, double yPos) {
if (chunk == 0) {
// Leave the first chunk empty
return;
} else if (chunk == 1) {
addLevelAsteroids(10, yPos, 0.0);
} else {
addLevelAsteroids(9 + chunk, yPos, 0.5);
}
}
void addLevelAsteroids(int numAsteroids, double yPos, double distribution) {
for (int i = 0; i < numAsteroids; i++) {
GameObjectType type = (randomDouble() < distribution) ? GameObjectType.asteroidBig : GameObjectType.asteroidSmall;
Point pos = new Point(randomSignedDouble() * 160.0,
yPos + _chunkSpacing * randomDouble());
_objectFactory.addGameObject(type, pos);
}
_objectFactory.addGameObject(GameObjectType.movingEnemy, new Point(0.0, yPos + 160.0));
}
void fire() {
Laser shot0 = new Laser(_objectFactory);
shot0.position = _level.ship.position + new Offset(17.0, -10.0);
_level.addChild(shot0);
Laser shot1 = new Laser(_objectFactory);
shot1.position = _level.ship.position + new Offset(-17.0, -10.0);
_level.addChild(shot1);
_effectPlayer.play(_sounds["laser"]);
}
void killShip() {
// Hide ship
_level.ship.visible = false;
_effectPlayer.play(_sounds["explosion"]);
// Add explosion
Explosion explo = new Explosion(_spritesGame);
explo.scale = 1.5;
explo.position = _level.ship.position;
_level.addChild(explo);
// Add flash
Flash flash = new Flash(size, 1.0);
addChild(flash);
// Set the state to game over
_gameOver = true;
// Return to main scene and report the score back in 2 seconds
new Timer(new Duration(seconds: 2), () { _gameOverCallback(_hud.score); });
}
}
class Level extends Node {
Level() {
position = new Point(160.0, 0.0);
}
Ship ship;
double scroll(double scrollSpeed) {
position += new Offset(0.0, scrollSpeed);
return position.y;
}
}
abstract class GameObject extends Node {
double radius = 0.0;
double removeLimit = 1280.0;
bool canDamageShip = true;
bool canBeDamaged = true;
double maxDamage = 3.0;
double damage = 0.0;
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);
}
removeFromParent();
}
}
int addDamage(double d) {
if (!canBeDamaged) return 0;
damage += d;
if (damage >= maxDamage) {
destroy();
return (maxDamage * 10).ceil();
}
return 10;
}
Explosion createExplosion() {
return null;
}
void paint(PaintingCanvas canvas) {
if (_drawDebug) {
canvas.drawCircle(Point.origin, radius, _paintDebug);
}
super.paint(canvas);
}
void setupActions() {
}
}
class Ship extends GameObject {
Ship(GameObjectFactory f) {
// Add main ship sprite
_sprt = new Sprite(f.sheet["ship.png"]);
_sprt.scale = 0.3;
_sprt.rotation = -90.0;
addChild(_sprt);
radius = 20.0;
canBeDamaged = false;
canDamageShip = false;
// Set start position
position = new Point(0.0, 50.0);
}
Sprite _sprt;
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));
}
}
class Laser extends GameObject {
double impact = 1.0;
Laser(GameObjectFactory f) {
// Add sprite
_sprt = new Sprite(f.sheet["laser.png"]);
_sprt.scale = 0.3;
_sprt.transferMode = sky.TransferMode.plus;
addChild(_sprt);
radius = 10.0;
removeLimit = 640.0;
canDamageShip = false;
canBeDamaged = false;
}
Sprite _sprt;
void move() {
position += new Offset(0.0, -10.0);
}
}
abstract class Obstacle extends GameObject {
Obstacle(this._f);
double explosionScale = 1.0;
GameObjectFactory _f;
Explosion createExplosion() {
SoundEffectPlayer.sharedInstance().play(_f.sounds["explosion"]);
Explosion explo = new Explosion(_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 (randomDouble() < 0.5) 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;
int alpha = ((200.0 * d) ~/ maxDamage).clamp(0, 200);
_sprt.colorOverlay = new Color.fromARGB(alpha, 255, 3, 86);
}
}
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 MovingEnemy extends Obstacle {
MovingEnemy(GameObjectFactory f) : super(f) {
_sprt = new Sprite(f.sheet["ship.png"]);
_sprt.scale = 0.2;
radius = 12.0;
maxDamage = 2.0;
addChild(_sprt);
constraints = [new ConstraintRotationToMovement(0.0, 0.5)];
}
void setupActions() {
List<Offset> offsets = [
new Offset(-160.0, 160.0),
new Offset(-80.0, -160.0),
new Offset(0.0, 160.0),
new Offset(80.0, -160.0),
new Offset(160.0, 160.0)];
List<Point> points = [];
for (Offset offset in offsets) {
points.add(position + offset);
}
ActionSpline spline = new ActionSpline((a) => position = a, points, 4.0);
actions.run(new ActionRepeatForever(spline));
}
Sprite _sprt;
}
enum GameObjectType {
asteroidBig,
asteroidSmall,
movingEnemy,
}
class GameObjectFactory {
GameObjectFactory(this.sheet, this.sounds, this.level);
SpriteSheet sheet;
Map<String,SoundEffect> sounds;
Level level;
void addGameObject(GameObjectType type, Point pos) {
GameObject obj;
if (type == GameObjectType.asteroidBig)
obj = new AsteroidBig(this);
else if (type == GameObjectType.asteroidSmall)
obj = new AsteroidSmall(this);
else if (type == GameObjectType.movingEnemy)
obj = new MovingEnemy(this);
obj.position = pos;
obj.setupActions();
level.addChild(obj);
}
}
// class MovingObstacle extends Obstacle {
// MovingObstacle(SpriteSheet sheet, Map<String,SoundEffect> effects, ObstacleType type) : super (sheet, effects, type);
//
// void setupAction() {
// actions.stopAll();
//
// List<Offset> offsets = [
// new Offset(-160.0, 160.0),
// new Offset(-80.0, -160.0),
// new Offset(0.0, 160.0),
// new Offset(80.0, -160.0),
// new Offset(160.0, 160.0)];
//
// List<Point> points = [];
// for (Offset offset in offsets) {
// points.add(position + offset);
// }
//
// ActionSpline spline = new ActionSpline((a) => position = a, points, 4.0);
// actions.run(new ActionRepeatForever(spline));
// }
// }
class StarField extends NodeWithSize {
sky.Image _image;
SpriteSheet _spriteSheet;
int _numStars;
bool _autoScroll;
List<Point> _starPositions;
List<double> _starScales;
List<Rect> _rects;
List<Color> _colors;
final double _padding = 50.0;
Size _paddedSize = Size.zero;
Paint _paint = new Paint()
..setFilterQuality(sky.FilterQuality.low)
..isAntiAlias = false
..setTransferMode(sky.TransferMode.plus);
StarField(this._spriteSheet, this._numStars, [this._autoScroll = false]) : super(Size.zero) {
_image = _spriteSheet.image;
}
void addStars() {
_starPositions = [];
_starScales = [];
_colors = [];
_rects = [];
size = spriteBox.visibleArea.size;
_paddedSize = new Size(size.width + _padding * 2.0,
size.height + _padding * 2.0);
for (int i = 0; i < _numStars; i++) {
_starPositions.add(new Point(randomDouble() * _paddedSize.width,
randomDouble() * _paddedSize.height));
_starScales.add(randomDouble() * 0.4);
_colors.add(new Color.fromARGB((255.0 * (randomDouble() * 0.5 + 0.5)).toInt(), 255, 255, 255));
_rects.add(_spriteSheet["star_${randomInt(2)}.png"].frame);
}
}
void spriteBoxPerformedLayout() {
addStars();
}
void paint(PaintingCanvas canvas) {
// Create a transform for each star
List<sky.RSTransform> transforms = [];
for (int i = 0; i < _numStars; i++) {
sky.RSTransform transform = new sky.RSTransform(
_starScales[i],
0.0,
_starPositions[i].x - _padding,
_starPositions[i].y - _padding);
transforms.add(transform);
}
// Draw the stars
canvas.drawAtlas(_image, transforms, _rects, _colors, sky.TransferMode.modulate, null, _paint);
}
void move(double dx, double dy) {
for (int i = 0; i < _numStars; i++) {
double xPos = _starPositions[i].x;
double yPos = _starPositions[i].y;
double scale = _starScales[i];
xPos += dx * scale;
yPos += dy * scale;
if (xPos >= _paddedSize.width) xPos -= _paddedSize.width;
if (xPos < 0) xPos += _paddedSize.width;
if (yPos >= _paddedSize.height) yPos -= _paddedSize.height;
if (yPos < 0) yPos += _paddedSize.height;
_starPositions[i] = new Point(xPos, yPos);
}
}
void update(double dt) {
if (_autoScroll) {
move(0.0, dt * 100.0);
}
}
}
class RepeatedImage extends Node {
Sprite _sprt0;
Sprite _sprt1;
RepeatedImage(sky.Image image, [sky.TransferMode mode = null]) {
_sprt0 = new Sprite.fromImage(image);
_sprt0.size = new Size(1024.0, 1024.0);
_sprt0.pivot = Point.origin;
_sprt1 = new Sprite.fromImage(image);
_sprt1.size = new Size(1024.0, 1024.0);
_sprt1.pivot = Point.origin;
_sprt1.position = new Point(0.0, -1024.0);
if (mode != null) {
_sprt0.transferMode = mode;
_sprt1.transferMode = mode;
}
addChild(_sprt0);
addChild(_sprt1);
}
void move(double dy) {
double yPos = (position.y + dy) % 1024.0;
position = new Point(0.0, yPos);
}
}
class Explosion extends Node {
Explosion(SpriteSheet sheet) {
// Add particles
ParticleSystem particlesDebris = new ParticleSystem(
sheet["explosion_particle.png"],
rotateToMovement: true,
startRotation:90.0,
startRotationVar: 0.0,
endRotation: 90.0,
startSize: 0.3,
startSizeVar: 0.1,
endSize: 0.3,
endSizeVar: 0.1,
numParticlesToEmit: 25,
emissionRate:1000.0,
greenVar: 127,
redVar: 127
);
particlesDebris.zPosition = 1010.0;
addChild(particlesDebris);
ParticleSystem particlesFire = new ParticleSystem(
sheet["fire_particle.png"],
colorSequence: new ColorSequence([new Color(0xffffff33), new Color(0xffff3333), new Color(0x00ff3333)], [0.0, 0.5, 1.0]),
numParticlesToEmit: 25,
emissionRate: 1000.0,
startSize: 0.5,
startSizeVar: 0.1,
endSize: 0.5,
endSizeVar: 0.1,
posVar: new Point(10.0, 10.0),
speed: 10.0,
speedVar: 5.0
);
particlesFire.zPosition = 1011.0;
addChild(particlesFire);
// Add ring
Sprite sprtRing = new Sprite(sheet["explosion_ring.png"]);
sprtRing.transferMode = sky.TransferMode.plus;
addChild(sprtRing);
Action scale = new ActionTween( (a) => sprtRing.scale = a, 0.2, 1.0, 1.5);
Action scaleAndRemove = new ActionSequence([scale, new ActionRemoveNode(sprtRing)]);
Action fade = new ActionTween( (a) => sprtRing.opacity = a, 1.0, 0.0, 1.5);
actions.run(scaleAndRemove);
actions.run(fade);
// Add streaks
for (int i = 0; i < 5; i++) {
Sprite sprtFlare = new Sprite(sheet["explosion_flare.png"]);
sprtFlare.pivot = new Point(0.3, 1.0);
sprtFlare.scaleX = 0.3;
sprtFlare.transferMode = sky.TransferMode.plus;
sprtFlare.rotation = randomDouble() * 360.0;
addChild(sprtFlare);
double multiplier = randomDouble() * 0.3 + 1.0;
Action scale = new ActionTween( (a) => sprtFlare.scaleY = a, 0.3 * multiplier, 0.8, 1.5 * multiplier);
Action scaleAndRemove = new ActionSequence([scale, new ActionRemoveNode(sprtFlare)]);
Action fadeIn = new ActionTween( (a) => sprtFlare.opacity = a, 0.0, 1.0, 0.5 * multiplier);
Action fadeOut = new ActionTween( (a) => sprtFlare.opacity = a, 1.0, 0.0, 1.0 * multiplier);
Action fadeInOut = new ActionSequence([fadeIn, fadeOut]);
actions.run(scaleAndRemove);
actions.run(fadeInOut);
}
}
}
class Hud extends Node {
SpriteSheet sheet;
Sprite sprtBgScore;
bool _dirtyScore = true;
int _score = 0;
int get score => _score;
set score(int score) {
_score = score;
_dirtyScore = true;
}
Hud(this.sheet) {
position = new Point(310.0, 10.0);
scale = 0.6;
sprtBgScore = new Sprite(sheet["scoreboard.png"]);
sprtBgScore.pivot = new Point(1.0, 0.0);
sprtBgScore.scale = 0.6;
addChild(sprtBgScore);
}
void update(double dt) {
// Update score
if (_dirtyScore) {
sprtBgScore.removeAllChildren();
String scoreStr = _score.toString();
double xPos = -50.0;
for (int i = scoreStr.length - 1; i >= 0; i--) {
String numStr = scoreStr.substring(i, i + 1);
Sprite numSprt = new Sprite(sheet["number_$numStr.png"]);
numSprt.position = new Point(xPos, 49.0);
sprtBgScore.addChild(numSprt);
xPos -= 37.0;
}
_dirtyScore = false;
}
}
}
class Flash extends NodeWithSize {
Flash(Size size, this.duration) : super(size) {
ActionTween fade = new ActionTween((a) => _opacity = a, 1.0, 0.0, duration);
ActionSequence seq = new ActionSequence([fade, new ActionRemoveNode(this)]);
actions.run(seq);
}
double duration;
double _opacity = 1.0;
Paint _cachedPaint = new Paint();
void paint(PaintingCanvas canvas) {
// Update the color
_cachedPaint.color = new Color.fromARGB((255.0 * _opacity).toInt(),
255, 255, 255);
// Fill the area
applyTransformForPivot(canvas);
canvas.drawRect(new Rect.fromLTRB(0.0, 0.0, size.width, size.height),
_cachedPaint);
}
}