diff --git a/examples/game/lib/action_spline.dart b/examples/game/lib/action_spline.dart new file mode 100644 index 0000000000..077b61ba35 --- /dev/null +++ b/examples/game/lib/action_spline.dart @@ -0,0 +1,57 @@ +part of sprites; + +Point _cardinalSplineAt(Point p0, Point p1, Point p2, Point p3, double tension, double t) { + double t2 = t * t; + double t3 = t2 * t; + + double s = (1.0 - tension) / 2.0; + + double b1 = s * ((-t3 + (2.0 * t2)) - t); + double b2 = s * (-t3 + t2) + (2.0 * t3 - 3.0 * t2 + 1.0); + double b3 = s * (t3 - 2.0 * t2 + t) + (-2.0 * t3 + 3.0 * t2); + double b4 = s * (t3 - t2); + + double x = p0.x * b1 + p1.x * b2 + p2.x * b3 + p3.x * b4; + double y = p0.y * b1 + p1.y * b2 + p2.y * b3 + p3.y * b4; + + return new Point(x, y); +} + +class ActionSpline extends ActionInterval { + ActionSpline(this.setter, this.points, double duration, [Curve curve]) : super(duration, curve) { + _dt = 1.0 / (points.length - 1.0); + } + + final Function setter; + final List points; + + double _dt; + + void update(double t) { + double tension = 0.5; + + int p; + double lt; + + if (t < 0.0) t = 0.0; + + if (t >= 1.0) { + p = points.length - 1; + lt = 1.0; + } else { + p = (t / _dt).floor(); + lt = (t - _dt * p) / _dt; + } + + Point p0 = points[(p - 1).clamp(0, points.length - 1)]; + Point p1 = points[(p + 0).clamp(0, points.length - 1)]; + Point p2 = points[(p + 1).clamp(0, points.length - 1)]; + Point p3 = points[(p + 2).clamp(0, points.length - 1)]; + + Point newPos = _cardinalSplineAt(p0, p1, p2, p3, tension, lt); + + //print("newPos: $newPos"); + + setter(newPos); + } +} diff --git a/examples/game/lib/constraint.dart b/examples/game/lib/constraint.dart new file mode 100644 index 0000000000..d1d3a82b24 --- /dev/null +++ b/examples/game/lib/constraint.dart @@ -0,0 +1,42 @@ +part of sprites; + +abstract class Constraint { + void preUpdate(Node node, double dt) { + } + + void constrain(Node node, double dt); +} + +double _dampenRotation(double src, double dst, double dampening) { + double delta = dst - src; + while (delta > 180.0) delta -= 360; + while (delta < -180) delta += 360; + delta *= dampening; + + return src + delta; +} + +class ConstraintRotationToMovement { + ConstraintRotationToMovement([this.dampening]); + final double dampening; + + Point _lastPosition; + + void preUpdate(Node node, double dt) { + _lastPosition = node.position; + } + + void constrain(Node node, double dt) { + assert(_lastPosition != null); + if (_lastPosition == node.position) return; + + // Get the target angle + Offset offset = node.position - _lastPosition; + double target = degrees(GameMath.atan2(offset.dy, offset.dx)); + + if (dampening == null) + node.rotation = target; + else + node.rotation = _dampenRotation(node.rotation, target, dampening); + } +} diff --git a/examples/game/lib/game_demo.dart b/examples/game/lib/game_demo.dart index ff839168e2..def48f867c 100644 --- a/examples/game/lib/game_demo.dart +++ b/examples/game/lib/game_demo.dart @@ -1,5 +1,6 @@ library game; +import 'dart:async'; import 'dart:sky' as sky; import 'dart:math' as Math; import 'sprites.dart'; @@ -10,4 +11,4 @@ import 'package:sky/widgets/navigator.dart'; import 'package:sky/animation/curves.dart'; import 'package:vector_math/vector_math_64.dart'; -part 'game_demo_world.dart'; +part 'game_demo_node.dart'; diff --git a/examples/game/lib/game_demo_node.dart b/examples/game/lib/game_demo_node.dart new file mode 100644 index 0000000000..d6c420c0d5 --- /dev/null +++ b/examples/game/lib/game_demo_node.dart @@ -0,0 +1,784 @@ +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); + + + addObjects(); + } + + // Resources + ImageMap _images; + Map _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 lasers = []; + for (Node node in _level.children) { + if (node is Laser) lasers.add(node); + } + + List 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 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); + } + } + + 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 VirtualJoystick extends NodeWithSize { + VirtualJoystick() : super(new Size(160.0, 160.0)) { + userInteractionEnabled = true; + handleMultiplePointers = false; + position = new Point(160.0, -20.0); + pivot = new Point(0.5, 1.0); + _center = new Point(size.width / 2.0, size.height / 2.0); + _handlePos = _center; + + _paintHandle = new Paint() + ..color=new Color(0xffffffff); + _paintControl = new Paint() + ..color=new Color(0xffffffff) + ..strokeWidth = 1.0 + ..setStyle(sky.PaintingStyle.stroke); + } + + Point value = Point.origin; + + bool _isDown = false; + bool get isDown => _isDown; + + Point _pointerDownAt; + Point _center; + Point _handlePos; + + Paint _paintHandle; + Paint _paintControl; + + bool handleEvent(SpriteBoxEvent event) { + if (event.type == "pointerdown") { + _pointerDownAt = event.boxPosition; + actions.stopAll(); + _isDown = true; + } + else if (event.type == "pointerup" || event.type == "pointercancel") { + _pointerDownAt = null; + value = Point.origin; + ActionTween moveToCenter = new ActionTween((a) => _handlePos = a, _handlePos, _center, 0.4, elasticOut); + actions.run(moveToCenter); + _isDown = false; + } else if (event.type == "pointermove") { + Offset movedDist = event.boxPosition - _pointerDownAt; + + value = new Point( + (movedDist.dx / 80.0).clamp(-1.0, 1.0), + (movedDist.dy / 80.0).clamp(-1.0, 1.0)); + + _handlePos = _center + new Offset(value.x * 40.0, value.y * 40.0); + } + return true; + } + + void paint(PaintingCanvas canvas) { + applyTransformForPivot(canvas); + canvas.drawCircle(_handlePos, 25.0, _paintHandle); + canvas.drawCircle(_center, 40.0, _paintControl); + } +} + +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); + } +} + +enum GameObjectType { + asteroidBig, + asteroidSmall, +} + +class GameObjectFactory { + GameObjectFactory(this.sheet, this.sounds, this.level); + + SpriteSheet sheet; + Map 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); + + obj.position = pos; + obj.setupActions(); + + level.addChild(obj); + } +} + +// class MovingObstacle extends Obstacle { +// MovingObstacle(SpriteSheet sheet, Map effects, ObstacleType type) : super (sheet, effects, type); +// +// void setupAction() { +// actions.stopAll(); +// +// List 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 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 _starPositions; + List _starScales; + List _rects; + List _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 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); + } +} diff --git a/examples/game/lib/game_demo_world.dart b/examples/game/lib/game_demo_world.dart deleted file mode 100644 index 6d0e6e5fde..0000000000 --- a/examples/game/lib/game_demo_world.dart +++ /dev/null @@ -1,752 +0,0 @@ -part of game; - -const double _steeringThreshold = 0.0; -const double _steeringMax = 150.0; - -// Random generator -Math.Random _rand = new Math.Random(); - -const double _gameSizeWidth = 1024.0; -const double _gameSizeHeight = 1024.0; - -const double _shipRadius = 30.0; -const double _lrgAsteroidRadius = 40.0; -const double _medAsteroidRadius = 20.0; -const double _smlAsteroidRadius = 10.0; -const double _maxAsteroidSpeed = 1.0; - -const int _lifeTimeLaser = 50; - -const int _numStarsInStarField = 150; - -const int _numFramesShieldActive = 60 * 5; -const int _numFramesShieldFlickers = 60; - -class GameDemoWorld extends NodeWithSize { - // Images - sky.Image _imgNebula; - - SpriteSheet _spriteSheet; - SpriteSheet _spriteSheetUI; - - Map _sounds; - SoundEffectPlayer _soundPool = SoundEffectPlayer.sharedInstance(); - - Navigator _navigator; - - // Inputs - double _joystickX = 0.0; - double _joystickY = 0.0; - - Node _gameLayer; - - Ship _ship; - Sprite _shield; - List _asteroids = []; - List _lasers = []; - StarField _starField; - Nebula _nebula; - - // Game state - int _numFrames = 0; - bool _isGameOver = false; - int _gameOverFrame; - int _currentLevel = 0; - - // Heads up display - Hud _hud; - - Function _gameOverCallback; - - GameDemoWorld(App app, this._navigator, ImageMap images, this._spriteSheet, this._spriteSheetUI, this._sounds, this._gameOverCallback) : super(new Size(_gameSizeWidth, _gameSizeHeight)) { - // Fetch images - _imgNebula = images["assets/nebula.png"]; - - _gameLayer = new Node(); - this.addChild(_gameLayer); - // Add ship - addShip(); - - // Add background - Sprite sprtBackground = new Sprite.fromImage(images["assets/starfield.png"]); - sprtBackground.position = new Point(512.0, 512.0); - sprtBackground.zPosition = -3.0; - addChild(sprtBackground); - - // Add starfield - _starField = new StarField(_spriteSheet, _numStarsInStarField); - _starField.zPosition = -2.0; - addChild(_starField); - - // Add nebula - addNebula(); - - userInteractionEnabled = true; - handleMultiplePointers = true; - - _hud = new Hud(_spriteSheetUI); - _hud.zPosition = 1000.0; - addChild(_hud); - - // Setup level - setupLevel(0); - } - - void setupLevel(int level) { - int numLargeAsteroids = 5 + level * 2; - int numMediumAsteroids = 5 + level * 2; - - // Add some asteroids to the game world - for (int i = 0; i < numLargeAsteroids; i++) { - addAsteroid(AsteroidSize.large); - } - for (int i = 0; i < numMediumAsteroids; i++) { - addAsteroid(AsteroidSize.medium); - } - - _numFrames = 0; - _shield.visible = true; - } - - // Methods for adding game objects - - void addAsteroid(AsteroidSize size, [Point pos]) { - Asteroid asteroid = new Asteroid(_spriteSheet, size); - asteroid.zPosition = 1.0; - if (pos != null) asteroid.position = pos; - _gameLayer.addChild(asteroid); - _asteroids.add(asteroid); - - // Animate asteroid into the scene - Action action = new ActionTween((a) => asteroid.scale = a, 0.0, 1.0, 1.0, bounceOut); - _gameLayer.actions.run(action); - } - - void addShip() { - Ship ship = new Ship(_spriteSheet["ship.png"]); - ship.zPosition = 10.0; - _gameLayer.addChild(ship); - _ship = ship; - - _shield = new Sprite(_spriteSheet["shield.png"]); - _shield.zPosition = 11.0; - _shield.scale = 0.5; - _shield.transferMode = sky.TransferMode.plus; - _gameLayer.addChild(_shield); - - Action rotate = new ActionRepeatForever(new ActionTween((a) => _shield.rotation = a, 0.0, 360.0, 1.0)); - actions.run(rotate); - } - - void addLaser() { - Laser laser = new Laser(_spriteSheet["laser.png"], _ship); - laser.zPosition = 8.0; - laser.constrainProportions = true; - _lasers.add(laser); - _gameLayer.addChild(laser); - - _soundPool.play(_sounds["laser"]); - } - - void addNebula() { - _nebula = new Nebula.withImage(_imgNebula); - _gameLayer.addChild(_nebula); - } - - void addExplosion(AsteroidSize asteroidSize, Point position) { - Node explosionNode = new Node(); - - // Add particles - ParticleSystem particlesDebris = new ParticleSystem( - _spriteSheet["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; - explosionNode.addChild(particlesDebris); - - ParticleSystem particlesFire = new ParticleSystem( - _spriteSheet["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; - explosionNode.addChild(particlesFire); - - - // Add ring - Sprite sprtRing = new Sprite(_spriteSheet["explosion_ring.png"]); - sprtRing.transferMode = sky.TransferMode.plus; - explosionNode.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(_spriteSheet["explosion_flare.png"]); - sprtFlare.pivot = new Point(0.3, 1.0); - sprtFlare.scaleX = 0.3; - sprtFlare.transferMode = sky.TransferMode.plus; - sprtFlare.rotation = _rand.nextDouble() * 360.0; - explosionNode.addChild(sprtFlare); - - double multiplier = _rand.nextDouble() * 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); - } - - explosionNode.position = position; - explosionNode.zPosition = 1010.0; - - if (asteroidSize == AsteroidSize.large) { - explosionNode.scale = 1.5; - } - - _gameLayer.addChild(explosionNode); - - _soundPool.play(_sounds["explosion"]); - } - - void update(double dt) { - // Move asteroids - for (Asteroid asteroid in _asteroids) { - asteroid.position = pointAdd(asteroid.position, asteroid._movementVector); - } - - // Move lasers and remove expired lasers - for (int i = _lasers.length - 1; i >= 0; i--) { - Laser laser = _lasers[i]; - laser.move(); - if (laser._frameCount > _lifeTimeLaser) { - laser.removeFromParent(); - _lasers.removeAt(i); - } - } - - // Apply thrust to ship - if (_joystickX != 0.0 || _joystickY != 0.0) { - _ship.thrust(_joystickX, _joystickY); - } - - // Move ship - _ship.move(); - _shield.position = _ship.position; - - // Check collisions between asteroids and lasers - for (int i = _lasers.length -1; i >= 0; i--) { - // Iterate over all the lasers - Laser laser = _lasers[i]; - - for (int j = _asteroids.length - 1; j >= 0; j--) { - // Iterate over all the asteroids - Asteroid asteroid = _asteroids[j]; - - // Check for collision - if (pointQuickDist(laser.position, asteroid.position) < laser.radius + asteroid.radius) { - // Remove laser - laser.removeFromParent(); - _lasers.removeAt(i); - - // Add asteroids and explosions - if (asteroid._asteroidSize == AsteroidSize.large) { - for (int a = 0; a < 3; a++) addAsteroid(AsteroidSize.medium, asteroid.position); - } - else if (asteroid._asteroidSize == AsteroidSize.medium) { - for (int a = 0; a < 5; a++) addAsteroid(AsteroidSize.small, asteroid.position); - } - - addExplosion(asteroid._asteroidSize, asteroid.position); - - // Remove asteroid - asteroid.removeFromParent(); - _asteroids.removeAt(j); - - // Scoring - if (asteroid._asteroidSize == AsteroidSize.large) - addScore(100); - else if (asteroid._asteroidSize == AsteroidSize.medium) - addScore(50); - else - addScore(10); - break; - } - } - } - - // Check collisions between asteroids and ship - if (_numFrames > _numFramesShieldActive) { - // Shield is no longer active - - for (int i = _asteroids.length - 1; i >= 0; i--) { - // Iterate over all the asteroids - Asteroid asteroid = _asteroids[i]; - - if (pointQuickDist(asteroid.position, _ship.position) < asteroid.radius + _ship.radius) { - killShip(); - } - } - } - - // Move objects to center camera and warp objects around the edges - centerCamera(); - warpObjects(); - - // Check for level up - if (_asteroids.length == 0) { - _currentLevel++; - setupLevel(_currentLevel); - } - - // Update shield - if (_numFrames > _numFramesShieldActive) _shield.visible = false; - else if (_numFrames > _numFramesShieldActive - _numFramesShieldFlickers) _shield.visible = !_shield.visible; - - // Check for exit back to main screen - if (_isGameOver && _numFrames - _gameOverFrame == 60) { - _navigator.pop(); - } - - _numFrames++; - } - - void centerCamera() { - const cameraDampening = 0.1; - Point delta = new Point(_gameSizeWidth/2 - _ship.position.x, _gameSizeHeight/2 - _ship.position.y); - delta = pointMult(delta, cameraDampening); - - for (Node child in _gameLayer.children) { - child.position = pointAdd(child.position, delta); - } - - // Update starfield - _starField.move(delta.x, delta.y); - } - - void warpObjects() { - for (Node child in _gameLayer.children) { - if (child.position.x < 0) child.position = pointAdd(child.position, new Point(_gameSizeWidth, 0.0)); - if (child.position.x >= _gameSizeWidth) child.position = pointAdd(child.position, new Point(-_gameSizeWidth, 0.0)); - if (child.position.y < 0) child.position = pointAdd(child.position, new Point(0.0, _gameSizeHeight)); - if (child.position.y >= _gameSizeHeight) child.position = pointAdd(child.position, new Point(0.0, -_gameSizeHeight)); - } - } - - void killShip() { - if (_isGameOver) return; - - // Set game over - _isGameOver = true; - _gameOverFrame = _numFrames; - _gameOverCallback(_hud.score); - - // Remove the ship - _ship.visible = false; - - // Add an explosion - addExplosion(AsteroidSize.large, _ship.position); - } - - // Handling controls - - void controlSteering(double x, double y) { - // Reset controls if it's game over - if (_isGameOver) { - x = y = 0.0; - } - - _joystickX = x; - _joystickY = y; - } - - void controlFire() { - // Don't shoot if it's game over - if (_isGameOver) return; - - addLaser(); - } - - // Handle pointer events - - int _firstPointer = -1; - int _secondPointer = -1; - Point _firstPointerDownPos; - - bool handleEvent(SpriteBoxEvent event) { - - Point pointerPos = convertPointToNodeSpace(event.boxPosition); - int pointer = event.pointer; - - switch (event.type) { - case 'pointerdown': - if (_firstPointer == -1) { - // Assign the first pointer - _firstPointer = pointer; - _firstPointerDownPos = pointerPos; - } - else if (_secondPointer == -1) { - // Assign second pointer - _secondPointer = pointer; - controlFire(); - } - else { - // There is a pointer used for steering, let's fire instead - controlFire(); - } - break; - case 'pointermove': - if (pointer == _firstPointer) { - // Handle turning control - double joystickX = 0.0; - double deltaX = pointerPos.x - _firstPointerDownPos.x; - if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) { - joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold); - if (joystickX > 1.0) joystickX = 1.0; - if (joystickX < -1.0) joystickX = -1.0; - } - - double joystickY = 0.0; - double deltaY = pointerPos.y - _firstPointerDownPos.y; - if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) { - joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold); - if (joystickY > 1.0) joystickY = 1.0; - if (joystickY < -1.0) joystickY = -1.0; - } - - controlSteering(joystickX, joystickY); - } - break; - case 'pointerup': - case 'pointercancel': - if (pointer == _firstPointer) { - // Un-assign the first pointer - _firstPointer = -1; - _firstPointerDownPos = null; - controlSteering(0.0, 0.0); - } - else if (pointer == _secondPointer) { - _secondPointer = -1; - } - break; - default: - break; - } - return true; - } - - // Scoring and HUD - void addScore(int score) { - _hud.score += score; - } -} - -// Game objects - -enum AsteroidSize { - small, - medium, - large, -} - -class Asteroid extends Sprite { - Point _movementVector; - AsteroidSize _asteroidSize; - double _radius; - - double get radius { - if (_radius != null) return _radius; - if (_asteroidSize == AsteroidSize.small) _radius = _smlAsteroidRadius; - else if (_asteroidSize == AsteroidSize.medium) _radius = _medAsteroidRadius; - else if (_asteroidSize == AsteroidSize.large) _radius = _lrgAsteroidRadius; - return _radius; - } - - Asteroid(SpriteSheet spriteSheet, AsteroidSize this._asteroidSize) { - size = new Size(radius * 2.0, radius * 2.0); - position = new Point(_gameSizeWidth * _rand.nextDouble(), _gameSizeHeight * _rand.nextDouble()); - rotation = 360.0 * _rand.nextDouble(); - - if (_asteroidSize == AsteroidSize.small) { - texture = spriteSheet["asteroid_small_${_rand.nextInt(2)}.png"]; - } else { - texture = spriteSheet["asteroid_big_${_rand.nextInt(2)}.png"]; - } - - _movementVector = new Point(_rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed, - _rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed); - - userInteractionEnabled = true; - - // Rotate forever - double direction = (_rand.nextBool()) ? 360.0 : -360.0; - ActionTween rot = new ActionTween( (a) => rotation = a, 0.0, direction, 2.0 * _rand.nextDouble() + 2.0); - ActionRepeatForever repeat = new ActionRepeatForever(rot); - actions.run(repeat); - } - - bool handleEvent(SpriteBoxEvent event) { - if (event.type == "pointerdown") { - actions.stopWithTag("fade"); - colorOverlay = new Color(0x99ffffff); - } - else if (event.type == "pointerup") { - // Fade out the color overlay - Action fadeOut = new ActionTween((a) => this.colorOverlay = a, new Color(0x99ffffff), new Color(0x00ffffff), 1.0); - Action fadeOutAndRemove = new ActionSequence([fadeOut, new ActionCallFunction(() => this.colorOverlay = null)]); - actions.run(fadeOutAndRemove, "fade"); - } - return false; - } -} - -class Ship extends Sprite { - Vector2 _movementVector; - double _rotationTarget; - double radius = _shipRadius; - - Ship(Texture img) : super(img) { - _movementVector = new Vector2.zero(); - rotation = _rotationTarget = 270.0; - - // Create sprite - size = new Size(_shipRadius * 2.0, _shipRadius * 2.0); - position = new Point(_gameSizeWidth/2.0, _gameSizeHeight/2.0); - } - - void thrust(double x, double y) { - _rotationTarget = convertRadians2Degrees(Math.atan2(y, x)); - Vector2 directionVector = new Vector2(x, y).normalize(); - _movementVector.addScaled(directionVector, 1.0); - } - - void move() { - position = new Point(position.x + _movementVector[0], position.y + _movementVector[1]); - _movementVector.scale(0.9); - - rotation = dampenRotation(rotation, _rotationTarget, 0.1); - } -} - -class Laser extends Sprite { - int _frameCount = 0; - Point _movementVector; - double radius = 20.0; - - Laser(Texture img, Ship ship) : super(img) { - size = new Size(30.0, 30.0); - position = ship.position; - rotation = ship.rotation + 90.0; - transferMode = sky.TransferMode.plus; - double rotRadians = convertDegrees2Radians(rotation); - _movementVector = pointMult(new Point(Math.sin(rotRadians), -Math.cos(rotRadians)), 10.0); - _movementVector = new Point(_movementVector.x + ship._movementVector[0], _movementVector.y + ship._movementVector[1]); - } - - void move() { - position = pointAdd(position, _movementVector); - _frameCount++; - } -} - -// Background starfield - -class StarField extends NodeWithSize { - sky.Image _image; - int _numStars; - bool _autoScroll; - List _starPositions; - List _starScales; - List _rects; - List _colors; - Paint _paint = new Paint() - ..setFilterQuality(sky.FilterQuality.low) - ..isAntiAlias = false - ..setTransferMode(sky.TransferMode.plus); - - StarField(SpriteSheet spriteSheet, this._numStars, [this._autoScroll = false]) : super(new Size(1024.0, 1024.0)) { - _starPositions = []; - _starScales = []; - _colors = []; - _rects = []; - - for (int i = 0; i < _numStars; i++) { - _starPositions.add(new Point(_rand.nextDouble() * _gameSizeWidth, _rand.nextDouble() * _gameSizeHeight)); - _starScales.add(_rand.nextDouble()); - _colors.add(new Color.fromARGB((255.0 * (_rand.nextDouble() * 0.5 + 0.5)).toInt(), 255, 255, 255)); - _rects.add(spriteSheet["star_${_rand.nextInt(2)}.png"].frame); - } - - _image = spriteSheet.image; - } - - void paint(PaintingCanvas canvas) { - // Create a transform for each star - List transforms = []; - for (int i = 0; i < _numStars; i++) { - sky.RSTransform transform = new sky.RSTransform(_starScales[i], 0.0, _starPositions[i].x, _starPositions[i].y); - 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 >= _gameSizeWidth) xPos -= _gameSizeWidth; - if (xPos < 0) xPos += _gameSizeWidth; - if (yPos >= _gameSizeHeight) yPos -= _gameSizeHeight; - if (yPos < 0) yPos += _gameSizeHeight; - - _starPositions[i] = new Point(xPos, yPos); - } - } - - void update(double dt) { - if (_autoScroll) { - move(dt * 100.0, 0.0); - } - } -} - -class Hud extends NodeWithSize { - SpriteSheet spriteSheetUI; - Sprite sprtBgScore; - Sprite sprtBgShield; - bool _dirtyScore = true; - - int _score = 0; - - int get score => _score; - - set score(int score) { - _score = score; - _dirtyScore = true; - } - - Hud(this.spriteSheetUI) : super(Size.zero) { - pivot = Point.origin; - - sprtBgScore = new Sprite(spriteSheetUI["scoreboard.png"]); - sprtBgScore.pivot = new Point(1.0, 0.0); - sprtBgScore.scale = 0.6; - addChild(sprtBgScore); - - sprtBgShield = new Sprite(spriteSheetUI["bar_shield.png"]); - sprtBgShield.pivot = Point.origin; - sprtBgShield.scale = 0.6; - // TODO: Add shield - //addChild(sprtBgShield); - } - - void spriteBoxPerformedLayout() { - // Set the size and position of HUD display - position = spriteBox.visibleArea.topLeft; - size = spriteBox.visibleArea.size; - - // Position hud objects - sprtBgShield.position = new Point(20.0, 20.0); - sprtBgScore.position = new Point(size.width - 20.0, 20.0); - } - - 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(spriteSheetUI["number_$numStr.png"]); - numSprt.position = new Point(xPos, 49.0); - sprtBgScore.addChild(numSprt); - xPos -= 37.0; - } - _dirtyScore = false; - } - // Update power bar - } -} - -class Nebula extends Node { - - Nebula.withImage(sky.Image img) { - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - Sprite sprt = new Sprite.fromImage(img); - sprt.transferMode = sky.TransferMode.plus; - sprt.pivot = Point.origin; - sprt.position = new Point(i * _gameSizeWidth - _gameSizeWidth, j * _gameSizeHeight - _gameSizeHeight); - addChild(sprt); - } - } - } -} - -// Convenience methods - -Point pointAdd(Point a, Point b) { - return new Point(a.x+ b.x, a.y + b.y); -} - -Point pointMult(Point a, double multiplier) { - return new Point(a.x * multiplier, a.y * multiplier); -} - -double dampenRotation(double src, double dst, double dampening) { - double delta = dst - src; - while (delta > 180.0) delta -= 360; - while (delta < -180) delta += 360; - delta *= dampening; - - return src + delta; -} - -double pointQuickDist(Point a, Point b) { - double dx = a.x - b.x; - double dy = a.y - b.y; - if (dx < 0.0) dx = -dx; - if (dy < 0.0) dy = -dy; - if (dx > dy) { - return dx + dy/2.0; - } - else { - return dy + dx/2.0; - } -} diff --git a/examples/game/lib/main.dart b/examples/game/lib/main.dart index 1a6aeb6bb1..1efda12931 100644 --- a/examples/game/lib/main.dart +++ b/examples/game/lib/main.dart @@ -77,10 +77,19 @@ main() async { class GameDemoApp extends App { NavigationState _navigationState; - GameDemoWorld _game; + NodeWithSize _game; int _lastScore = 0; void initState() { + // _game = new GameDemoNode( + // _imageMap, + // _spriteSheet, + // _spriteSheetUI, + // _sounds, + // (lastScore) { + // setState(() {_lastScore = lastScore;}); + // }); + _navigationState = new NavigationState([ new Route( name: '/', @@ -112,24 +121,23 @@ class GameDemoApp extends App { } Widget _buildGameScene(navigator, route) { - return new SpriteWidget(_game); + return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth); } Widget _buildMainScene(navigator, route) { return new Stack([ - new SpriteWidget(new MainScreenBackground()), + new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth), new Flex([ new TextureButton( onPressed: () { - _game = new GameDemoWorld( - _app, - navigator, + _game = new GameDemoNode( _imageMap, _spriteSheet, _spriteSheetUI, _sounds, (lastScore) { setState(() {_lastScore = lastScore;}); + navigator.pop(); } ); navigator.pushNamed('/game'); @@ -243,14 +251,19 @@ class _TextureButtonToken { } class MainScreenBackground extends NodeWithSize { - MainScreenBackground() : super(new Size(1024.0, 1024.0)) { - Sprite sprtBackground = new Sprite.fromImage(_imageMap['assets/starfield.png']); - sprtBackground.position = new Point(512.0, 512.0); - addChild(sprtBackground); + MainScreenBackground() : super(new Size(320.0, 320.0)) { + // Sprite sprtBackground = new Sprite.fromImage(_imageMap['assets/starfield.png']); + // sprtBackground.position = new Point(160.0, 160.0); + // addChild(sprtBackground); assert(_spriteSheet.image != null); StarField starField = new StarField(_spriteSheet, 200, true); addChild(starField); } + + void paint(PaintingCanvas canvas) { + canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, 320.0, 320.0), new Paint()..color=new Color(0xff000000)); + super.paint(canvas); + } } diff --git a/examples/game/lib/node.dart b/examples/game/lib/node.dart index b03e36abaf..bad9f4bfc2 100644 --- a/examples/game/lib/node.dart +++ b/examples/game/lib/node.dart @@ -76,6 +76,25 @@ class Node { return _actions; } + List _constraints; + + List get constraints { + return _constraints; + } + + set constraints(List constraints) { + _constraints = constraints; + if (_spriteBox != null) _spriteBox._constrainedNodes = null; + } + + void applyConstraints(double dt) { + if (_constraints == null) return; + + for (Constraint constraint in _constraints) { + constraint.constrain(this, dt); + } + } + // Constructors /// Creates a new [Node] without any transformation. diff --git a/examples/game/lib/sprite_box.dart b/examples/game/lib/sprite_box.dart index a18f3a89d7..264cc5f3a2 100644 --- a/examples/game/lib/sprite_box.dart +++ b/examples/game/lib/sprite_box.dart @@ -74,6 +74,8 @@ class SpriteBox extends RenderBox { List _actionControllers; + List _constrainedNodes; + Rect _visibleArea; Rect get visibleArea { @@ -139,11 +141,13 @@ class SpriteBox extends RenderBox { _registerNode(Node node) { _actionControllers = null; _eventTargets = null; + if (node == null || node.constraints != null) _constrainedNodes = null; } _deregisterNode(Node node) { _actionControllers = null; _eventTargets = null; + if (node == null || node.constraints != null) _constrainedNodes = null; } // Event handling @@ -353,8 +357,10 @@ class SpriteBox extends RenderBox { _frameRate = 1.0/delta; + _callConstraintsPreUpdate(delta); _runActions(delta); _callUpdate(_rootNode, delta); + _callConstraintsConstrain(delta); // Schedule next update _scheduleTick(); @@ -392,6 +398,42 @@ class SpriteBox extends RenderBox { } } + void _callConstraintsPreUpdate(double dt) { + if (_constrainedNodes == null) { + _constrainedNodes = []; + _addConstrainedNodes(_rootNode, _constrainedNodes); + } + + for (Node node in _constrainedNodes) { + for (Constraint constraint in node.constraints) { + constraint.preUpdate(node, dt); + } + } + } + + void _callConstraintsConstrain(double dt) { + if (_constrainedNodes == null) { + _constrainedNodes = []; + _addConstrainedNodes(_rootNode, _constrainedNodes); + } + + for (Node node in _constrainedNodes) { + for (Constraint constraint in node.constraints) { + constraint.constrain(node, dt); + } + } + } + + void _addConstrainedNodes(Node node, List nodes) { + if (node._constraints != null && node._constraints.length > 0) { + nodes.add(node); + } + + for (Node child in node.children) { + _addConstrainedNodes(child, nodes); + } + } + void _callSpriteBoxPerformedLayout(Node node) { node.spriteBoxPerformedLayout(); for (Node child in node.children) { diff --git a/examples/game/lib/sprites.dart b/examples/game/lib/sprites.dart index e26bb4517b..7afdf904c6 100644 --- a/examples/game/lib/sprites.dart +++ b/examples/game/lib/sprites.dart @@ -22,6 +22,8 @@ import 'package:sky_services/media/media.mojom.dart'; import 'package:vector_math/vector_math.dart'; part 'action.dart'; +part 'constraint.dart'; +part 'action_spline.dart'; part 'color_secuence.dart'; part 'image_map.dart'; part 'layer.dart'; diff --git a/examples/game/lib/util.dart b/examples/game/lib/util.dart index fc8f08c913..71edbad109 100644 --- a/examples/game/lib/util.dart +++ b/examples/game/lib/util.dart @@ -80,4 +80,21 @@ class GameMath { } } } + + static double pointQuickDist(Point a, Point b) { + double dx = a.x - b.x; + double dy = a.y - b.y; + if (dx < 0.0) dx = -dx; + if (dy < 0.0) dy = -dy; + if (dx > dy) { + return dx + dy/2.0; + } + else { + return dy + dx/2.0; + } + } + + static double filter (double a, double b, double filterFactor) { + return (a * (1-filterFactor)) + b * filterFactor; + } }