Realizzare Puzzle Bobble (Bust a Move) [5 di ?]

Nonostante la notevole attesa tra una lezione e l’altra, che spero i miei pochi lettori sopporteranno, la realizzazione di questa versione di Puzzle Bobble – Bust A Move prosegue.

Oggi aggiungeremo il “Cannone spara bolle”, che ci fornirà un’idea molto chiara di quella che sarà l’esperienza di gioco una volta completato tutto il ciclo di Tutorial.Ho deciso che il controllo di gioco sarà affidato al Mouse, molto semplicemente il giocatore utilizzerà il puntatore per definire la rotazione del Cannone e con un click sparerà la bolla.

Per fare in modo che il Cannone (che è uno Sprite) segua la rotazione del Mouse dobbiamo calcolare qual’è l’angolo formato dal Punto di Registrazione del Cannone stesso assieme al Punto variabile rappresentato dalle coordinate del Mouse.

Angolo tra due Punti in un Sistema di Riferimento Cartesiano

Angolo tra due Punti in un Sistema di Riferimento Cartesiano

Tutti quelli che ricordano ancora le lezioni di Trigonometria sapranno che l’informazione che ci serve è facilmente ottenibile attraverso il calcolo dell’Arco Tangente.

Lo Standard ECMAScript contiene la definizione dell’Oggetto Math, per fortuna, questo Oggetto contiene in sé il Metodo che useremo: si chiama atan2.

Il Metodo atan2 dell’Oggetto Math calcola e restituisce l’angolo, espresso in radianti, del punto y/x, misurato in senso antiorario partendo dall’asse x di un cerchio (dove 0,0 rappresenta il centro del cerchio). Il valore restituito è compreso tra pigreco positivo e pigreco negativo.

Una volta ottenuto l’angolo in Radianti dobbiamo convertirne il valore in Gradi, ma il più è fatto.

Il sistema per convertire il valore di un Angolo da Radianti a Gradi è il seguente:

AngoloInRadianti * 180 / PiGreco

Adesso sappiamo tutto quello che ci serve per creare uno Sprite in grado di Ruotare nella direzione in cui si trova il puntatore del Mouse, non ci rimane che aprire il nostro Editor e scrivere la Classe che definisce il Cannone, e che si chiama appunto Cannon:

package pb {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;

    public class Cannon extends Sprite {
        private var _diameter:uint = 30;
        private var _bubble:Sphere;

        public function Cannon(d) {
            _diameter = d;
            this.addEventListener(Event.ADDED_TO_STAGE, drawCannon);
        }

        private function drawCannon(e:Event) {
            this.graphics.clear();
            this.graphics.beginFill(0x006699, 1);
            this.graphics.moveTo(0, _diameter*-1.30);
            this.graphics.lineTo(_diameter*0.18,_diameter*-1);
            this.graphics.lineTo(_diameter*-0.18,_diameter*-1);
            this.graphics.lineTo(0, _diameter*-1.30);
            this.graphics.endFill();
            stage.addEventListener(Event.RESIZE, placeMe);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, rotateMe);
            stage.addEventListener(MouseEvent.CLICK, shotSphere);
            placeMe();
        }

        public function placeMe(e:Event = null) {
            this.x = stage.stageWidth/2;
            this.y = stage.stageHeight-_diameter;
        }

        public function rotateMe(e:MouseEvent) {
            var mouseAngle = ((Math.atan2(stage.mouseY - this.y, stage.mouseX - this.x)) * (180 / Math.PI));
            if (stage.mouseY < this.y) {
                this.rotation = mouseAngle + 90;
            }
        }

        public function createSphere(c:String) {
            _bubble = new Sphere(c, _diameter);
            this.addChild(_bubble);
        }

        private function shotSphere(e:MouseEvent) {
            if (_bubble) {
                _bubble.x = this.x;
                _bubble.y = this.y;
                _bubble.direct = (this.rotation - 90) / 180 * Math.PI;
                this.parent.addChild(_bubble);
                _bubble.lifeStatus = 'moving';
                _bubble = null;
            }
        }
    }
}

Il compito di creare nuove Bolle è stato spostato all’interno di questa Classe perché è qui che le Sfere nuove saranno istanziate, inoltre nel momento in cui l’utente fa click con il Mouse la Sfera viene liberata e il valore di rotazione del Cannone viene trasferito alla nuova Proprietà direct delle Classe Sphere. Sarà la Classe Sphere a contenere il Metodo che si occupa del movimento delle Istanze della Sfera in base al valore di questa Proprietà.

Quindi ho modificato la Classe Sphere per fare in modo di gestire questo nuovo comportamento:

package pb {
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.events.Event;
    import fl.transitions.Tween;
    import fl.transitions.TweenEvent;
    import fl.transitions.easing.*;

    public class Sphere extends Sprite{
        public var color:String; /// Il colore della sfera
        public var diameter:uint; /// Il diametro della sfera
        public var group:uint = 0; /// Il gruppo colore cui la sfera appartiene
        public var topconnected:uint = 1; /// Vale zero se la sfera ha una connessione al soffitto
        private var _lifeStatus:String = 'born'; /// born|moving|alive|falling|exploding indica lo stato in cui si trova la sfera
        private var _context:Main; //// Il contenitore
        private var _direct:Number = Math.PI/-2; //// La direzione
        private var _speedX:Number = -1; //// La velocità X
        private var _speedY:Number = 0; //// La velocità Y
        private var _speed:uint = 5;
        private var _explosionTween:Tween;
        private var _fallTween:Tween;

        public function Sphere(c:String = '000000', d:uint = 30) {
            this.addEventListener(Event.ADDED, added);
            color = c;
            diameter = d;
            /// Disegno la parte non visibile
            this.graphics.beginFill(0x000000,0);
            this.graphics.drawCircle(0,0,diameter*0.8);
            this.graphics.endFill();
            /// Disegno la parte visibile
            this.graphics.beginFill(parseInt('0x'+color),1);
            this.graphics.drawCircle(0,0,diameter/2);
            this.graphics.endFill();
        }

        private function added(e:Event) {
            if (this.parent is Main) {
                _context = this.parent as Main;
            }
        }

        public function explode() {
            _explosionTween = new Tween(this, "scaleX", Strong.easeOut, 1, 2, 1, true);
            _explosionTween.addEventListener(TweenEvent.MOTION_CHANGE, explodingTween);
            _explosionTween.addEventListener(TweenEvent.MOTION_FINISH, removeMe);
        }

        private function explodingTween(e:TweenEvent) {
            this.scaleY = this.scaleX;
            this.alpha = 2-this.scaleY;
        }

        public function fall() {
            var myY:Number = this.y;
            _fallTween = new Tween(this, "alpha", Strong.easeOut, 1, 0, 1, true);
            _fallTween.addEventListener(TweenEvent.MOTION_CHANGE, fallingTween);
            _fallTween.addEventListener(TweenEvent.MOTION_FINISH, removeMe);
        }

        private function fallingTween(e:TweenEvent) {
            this.y ++;
        }

        private function moveMe(e:Event) {
            this.x += _speedX;
            this.y += _speedY;
            if (_context.walls.hitTestPoint(this.x,this.y,true)) {
                this._speedX *= -1;
            } else if (_context.grid.hitTestPoint(this.x,this.y,true)) {
                _context.grid.addBubble(this.x, this.y, this);
                _context.createBubble();
            }
        }

        private function removeMe(e:* = null) {
            if (this.parent) {
                this.parent.removeChild(this);
            }
        }

        public function set lifeStatus(l:String):void {
            switch(l) {
                case 'falling':
                case 'exploding':
                case 'alive':
                try {
                    this.removeEventListener(Event.ENTER_FRAME, moveMe);
                } catch(e) {}
                case 'born':
                _lifeStatus = l;
                break;
                case 'moving':
                _lifeStatus = l;
                this.addEventListener(Event.ENTER_FRAME, moveMe);
                break;
                default:
                throw new Error('lifeStatus must have one of these values: born|moving|alive|falling|exploding');
            }
        }

        public function get lifeStatus():String {
            return _lifeStatus;
        }

        public function set direct(d:Number) {
            _direct = d;
            _speedX = Math.cos(_direct)*_speed;
            _speedY = Math.sin(_direct)*_speed;
        }
    }
}

Non è cambiato molto rispetto a prima: c’è una gestione più completa del valore delle Proprietà lifeStatus e un Metodo moveMe che gestisce il movimento della Sfera una volta che questa è stata sparata.

Per evitare che la Sfera uscisse dallo schermo dal bordo destro o sinistro, perdendosi nel vuoto, mi serviva qualcosa su cui farla rimbalzare, per questo motivo ho pensato di aggiungere un ulteriore elemento, chiamato Walls che rappresenta il muro a destra e a sinistra dello schermo. Si tratta di un Oggetto molto semplice, ecco il codice:

package pb {
    import flash.display.Sprite;
    import flash.events.Event;

    public class Walls extends Sprite {
        private var _diameter:uint = 30;

        public function Walls(d) {
            _diameter = d;
            this.addEventListener(Event.ADDED_TO_STAGE, drawWalls);
        }

        public function drawWalls(e:Event) {
            stage.addEventListener(Event.RESIZE, drawWalls);
            this.graphics.clear();
            this.graphics.beginFill(0x000000, 0.1);
            this.graphics.drawRect(0,0,_diameter/2,stage.stageHeight);
            this.graphics.drawRect(stage.stageWidth-(_diameter/2),0,_diameter/2,stage.stageHeight);
            this.graphics.endFill();
        }
    }
}

Ecco tutti gli elementi creati fino a questo punto:

No flash, please!

Purtroppo Adobe Flash Player non è più benvoluto come una volta, per questo motivo ho disabilitato tutte le applicazioni Flash presenti sul sito. Se vuoi puoi comunque scaricare il file SWF che si trovava qui, clicca qui per far partire il download

 

Inserisci nome e indirizzo email per iscriverti alla mia newsletter e ricevere il file immediatamente.
In breve: i dati inseriti in questo modulo saranno utilizzati per inviarti il link per scaricare il file che desideri, saranno conservati da un servizio esterno che si chiama MailChimp e in qualsiasi momento potrai cancellare la tua iscrizione al seguente link: https://danielealessandra.us7.list-manage.com/unsubscribe?u=546bebc381e525372d2120083&id=326af7d230.

Puoi leggere l'informativa completa cliccando sul link Privacy Policy che trovi dovunque su questa pagina, e comunque visitando in qualsiasi momento l'indirizzo https://www.danielealessandra.com/privacy-policy/
Ho letto e accetto l’informativa sulla privacy.

Potrebbero interessarti anche...

2 Risposte

  1. Bajram Hushi ha detto:

    bellissima guida attendo con impazienza il prossimo articolo

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.