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; Navigator _navigator; // Inputs double _joystickX = 0.0; double _joystickY = 0.0; Node _gameLayer; Ship _ship; Sprite _shield; List<Asteroid> _asteroids = []; List<Laser> _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._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); } 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); } 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<Point> _starPositions; List<double> _starScales; List<Rect> _rects; List<Color> _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<sky.RSTransform> 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) { 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; } }