diff --git a/sdk/example/game/lib/action.dart b/sdk/example/game/lib/action.dart index f0763c02c01..0c6119d528a 100644 --- a/sdk/example/game/lib/action.dart +++ b/sdk/example/game/lib/action.dart @@ -1,8 +1,9 @@ part of sprites; -typedef void PointSetter(Point point); +typedef void ActionCallback(); abstract class Action { + Object _tag; bool _finished = false; void step(double dt); @@ -123,7 +124,7 @@ abstract class ActionInstant extends Action { } class ActionCallFunction extends ActionInstant { - Function _function; + ActionCallback _function; ActionCallFunction(this._function); @@ -132,10 +133,10 @@ class ActionCallFunction extends ActionInstant { } } -class ActionRemoveFromParent extends ActionInstant { +class ActionRemoveNode extends ActionInstant { Node _node; - ActionRemoveFromParent(this._node); + ActionRemoveNode(this._node); void fire() { _node.removeFromParent(); @@ -186,6 +187,12 @@ class ActionTween extends ActionInterval { _delta = new Point(xEnd - xStart, yEnd - yStart); } else if (startVal is double) { _delta = endVal - startVal; + } else if (startVal is Color) { + int aDelta = endVal.alpha - startVal.alpha; + int rDelta = endVal.red - startVal.red; + int gDelta = endVal.green - startVal.green; + int bDelta = endVal.blue - startVal.blue; + _delta = new _ColorDiff(aDelta, rDelta, gDelta, bDelta); } else { assert(false); } @@ -195,15 +202,24 @@ class ActionTween extends ActionInterval { var newVal; if (startVal is Point) { + // Point double xStart = startVal.x; double yStart = startVal.y; double xDelta = _delta.x; double yDelta = _delta.y; - newVal = new Point(xStart + xDelta * t, yStart + yDelta * t); } else if (startVal is double) { + // Doubles newVal = startVal + _delta * t; + } else if (startVal is Color) { + // Colors + int aNew = (startVal.alpha + (_delta.alpha * t).toInt()).clamp(0, 255); + int rNew = (startVal.red + (_delta.red * t).toInt()).clamp(0, 255); + int gNew = (startVal.green + (_delta.green * t).toInt()).clamp(0, 255); + int bNew = (startVal.blue + (_delta.blue * t).toInt()).clamp(0, 255); + newVal = new Color.fromARGB(aNew, rNew, gNew, bNew); } else { + // Oopses assert(false); } @@ -217,11 +233,30 @@ class ActionController { ActionController(); - void run(Action action) { + void run(Action action, [Object tag]) { + action._tag = tag; action.update(0.0); _actions.add(action); } + void stop(Action action) { + _actions.remove(action); + } + + void stopWithTag(Object tag) { + for (int i = _actions.length - 1; i >= 0; i--) { + Action action = _actions[i]; + if (action._tag == tag) { + _actions.removeAt(i); + print("removing tag: $tag"); + } + } + } + + void stopAll() { + _actions.clear(); + } + void step(double dt) { for (int i = _actions.length - 1; i >= 0; i--) { Action action = _actions[i]; @@ -232,4 +267,13 @@ class ActionController { } } } +} + +class _ColorDiff { + final int alpha; + final int red; + final int green; + final int blue; + + _ColorDiff(this.alpha, this.red, this.green, this.blue); } \ No newline at end of file diff --git a/sdk/example/game/lib/game_demo.dart b/sdk/example/game/lib/game_demo.dart index 840e6d59b0f..6ca7d3364ec 100644 --- a/sdk/example/game/lib/game_demo.dart +++ b/sdk/example/game/lib/game_demo.dart @@ -3,6 +3,7 @@ library game; import 'dart:sky'; import 'dart:math' as Math; import 'package:vector_math/vector_math_64.dart'; +import 'package:sky/widgets/widget.dart'; import 'sprites.dart'; import 'package:sky/rendering/object.dart'; diff --git a/sdk/example/game/lib/game_demo_world.dart b/sdk/example/game/lib/game_demo_world.dart index 45bc09a4bf1..2110689fcba 100644 --- a/sdk/example/game/lib/game_demo_world.dart +++ b/sdk/example/game/lib/game_demo_world.dart @@ -19,7 +19,11 @@ const int _lifeTimeLaser = 50; const int _numStarsInStarField = 150; +const int _numFramesShieldActive = 60 * 5; +const int _numFramesShieldFlickers = 60; + class GameDemoWorld extends NodeWithSize { + App _app; // Images Image _imgNebula; @@ -34,12 +38,17 @@ class GameDemoWorld extends NodeWithSize { Node _gameLayer; Ship _ship; + Sprite _shield; List _asteroids = []; List _lasers = []; StarField _starField; Nebula _nebula; + + // Game state + int _numFrames = 0; + bool _isGameOver = false; - GameDemoWorld(ImageMap images, this._spriteSheet) : super(new Size(_gameSizeWidth, _gameSizeHeight)) { + GameDemoWorld(this._app, ImageMap images, this._spriteSheet) : super(new Size(_gameSizeWidth, _gameSizeHeight)) { // Fetch images _imgNebula = images["res/nebula.png"]; @@ -91,6 +100,15 @@ class GameDemoWorld extends NodeWithSize { 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 = 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() { @@ -107,22 +125,51 @@ class GameDemoWorld extends NodeWithSize { } void addExplosion(AsteroidSize asteroidSize, Point position) { + Node explosionNode = new Node(); + // Add particles - ParticleSystem particles = 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); - particles.zPosition = 1010.0; - particles.position = position; - _gameLayer.addChild(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.position = position; sprtRing.transferMode = TransferMode.plus; - _gameLayer.addChild(sprtRing); + explosionNode.addChild(sprtRing); Action scale = new ActionTween( (a) => sprtRing.scale = a, 0.2, 1.0, 1.5); - Action scaleAndRemove = new ActionSequence([scale, new ActionRemoveFromParent(sprtRing)]); + 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); @@ -131,22 +178,30 @@ class GameDemoWorld extends NodeWithSize { for (int i = 0; i < 5; i++) { Sprite sprtFlare = new Sprite(_spriteSheet["explosion_flare.png"]); sprtFlare.pivot = new Point(0.3, 1.0); - sprtFlare.position = position; sprtFlare.scaleX = 0.3; sprtFlare.transferMode = TransferMode.plus; sprtFlare.rotation = _rand.nextDouble() * 360.0; - _gameLayer.addChild(sprtFlare); + 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 ActionRemoveFromParent(sprtFlare)]); + 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); } void update(double dt) { @@ -172,6 +227,7 @@ class GameDemoWorld extends NodeWithSize { // Move ship _ship.move(); + _shield.position = _ship.position; // Check collisions between asteroids and lasers for (int i = _lasers.length -1; i >= 0; i--) { @@ -206,9 +262,29 @@ class GameDemoWorld extends NodeWithSize { } } + // 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(); + + // Update shield + if (_numFrames > _numFramesShieldActive) _shield.visible = false; + else if (_numFrames > _numFramesShieldActive - _numFramesShieldFlickers) _shield.visible = !_shield.visible; + + _numFrames++; } void centerCamera() { @@ -233,14 +309,35 @@ class GameDemoWorld extends NodeWithSize { } } + void killShip() { + if (_isGameOver) return; + + // Set game over + _isGameOver = true; + + // 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(); } @@ -251,6 +348,7 @@ class GameDemoWorld extends NodeWithSize { Point _firstPointerDownPos; bool handleEvent(SpriteBoxEvent event) { + Point pointerPos = convertPointToNodeSpace(event.boxPosition); int pointer = event.pointer; @@ -358,10 +456,14 @@ class Asteroid extends Sprite { bool handleEvent(SpriteBoxEvent event) { if (event.type == "pointerdown") { - colorOverlay = new Color(0x99ff0000); + actions.stopWithTag("fade"); + colorOverlay = new Color(0x99ffffff); } else if (event.type == "pointerup") { - colorOverlay = null; + // 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; } @@ -370,6 +472,7 @@ class Asteroid extends Sprite { class Ship extends Sprite { Vector2 _movementVector; double _rotationTarget; + double radius = _shipRadius; Ship(Texture img) : super(img) { _movementVector = new Vector2.zero(); diff --git a/sdk/example/game/lib/node_with_size.dart b/sdk/example/game/lib/node_with_size.dart index 0e56ded4012..061dec40310 100644 --- a/sdk/example/game/lib/node_with_size.dart +++ b/sdk/example/game/lib/node_with_size.dart @@ -3,7 +3,7 @@ part of sprites; /// The super class of any [Node] that has a size. /// /// NodeWithSize adds the ability for a node to have a size and a pivot point. -abstract class NodeWithSize extends Node { +class NodeWithSize extends Node { /// Changing the size will affect the size of the rendering of the node. /// diff --git a/sdk/example/game/lib/particle_system.dart b/sdk/example/game/lib/particle_system.dart index fe6adc3f373..2ad4cf69da9 100644 --- a/sdk/example/game/lib/particle_system.dart +++ b/sdk/example/game/lib/particle_system.dart @@ -283,8 +283,6 @@ void drawAtlas(Canvas canvas, Image image, List transforms, List