• Programmazione Android
  • CORSI ONLINE
  • Web Agency

Logo

Corsi di programmazione web e mobile online
Navigation
  • Home
  • CORSI ONLINE
  • Tutorial Pratici
  • GUIDE COMPLETE
    • Corso completo di C
    • Corso videogame con Cocos2d
    • Programmazione Cocoa Touch
  • Sezioni
    • Libri e manuali
    • Tips & Tricks
    • Risorse utili
    • Strumenti di Sviluppo
    • Materiale OpenSource
    • Framework
    • Guide Teoriche
    • Guide varie
    • Grafica e Design
    • iPad
    • News
    • Video Tutorial
    • Windows Phone
  • Pubblicità
  • About
    • Chi siamo
    • Pubblicazioni
    • Collabora
    • Sostieni devAPP

T#104 – CAEmitterLayer: Creiamo un simpatico generatore di fiocchi di neve con il nostro iPhone

By Costantino Pistagna | on 15 Dicembre 2011 | 7 Comments
Senza categoria

tutorial-iOS-dev-advanced-devAPP In questo periodo di grandi incertezze in cui lo spread altalenante sembra essere l’unico vero problema con cui confrontarsi, c’è ancora qualcosa con cui divertirsi quando si è stanchi la sera. No! …non è la TV e neanche la PS3… è la programmazione. Da sempre il periodo di Natale è uno dei miei più proficui momenti per programmare. Ricordo ancora il clima di festa e l’odore del pino, rigorosamente naturale, che si respirava a casa da giorno 8 Dicembre in poi quando ero piccolo. Ogni anno era un’attesa senza fine… ogni anno c’era un nuovo pezzo di tecnologia informatica da aggiungere alla mia collezione. Purtroppo, però, la sete di conoscenza andava sempre più veloce dei naturali periodi di rotazione della terra e così capitava spesso che già alla metà dell’anno appena entrato, avevo voglia/bisogno di qualcosa di nuovo con cui giocare. Erano bei tempi.

Da un pò di tempo questa cosa non capita più, forse è colpa dell’età che avanza. A voler essere sinceri, la reale motivazione sono sicuro coincide con l’essermi imbattuto in Cocoa ed Objective-C (con relativo hardware, si intende). Così, il periodo delle feste natalizie è diventato una piacevole occasione per sedermi davanti al fuoco caldo del camino, giocando con qualche nuovo oggetto del framework che sino ad ora mi era passato sfuggente sotto gli occhi a causa della mancanza di tempo.

Rilasattevi e fatevi un buon the caldo. E’ il momento di scaldare il nostro MacBook. Tra qualche minuto faremo quello che qualunque buon programmatore vorrebbe edovrebbe fare in questo periodo: creare un bel generatore di fiocchi di neve. Meglio se accellerato in OpenGL. D’altrocanto, visto che siamo aspiranti programmatori iOS, ottempereremo al nostro dovere sfruttando il nostro dispositivo preferito. Buonalettura.


banner-allertasoglie-push-tre

Preparazione del progetto

CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-01 Quello che vogliamo realizzare sarà qualcosa di questo tipo: al tocco dello schermo, cercheremo di creare una caduta di fiocchi di neve.

Sarà un pò come avere la nostra neve personale sempre a portata di mano, con cui fare divertire i nostri amici durante le feste di natale.

Creiamo un nuovo progetto, scegliendo un template il più generale possibile (Empty Application):


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-02

Chiamiamo il nostro progetto: SnowFlakes.


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-03

Abbiate cura di deselezionare l’opzione ARC (Automatic Reference Counting). Il motivo per cui è meglio deselezionarlo lo raccontiamo un’altra volta.

Se vorrete installare l’applicativo realizzato su dispositivi o regalarlo ad amici con una IPA, date un company identifier valido al vostro applicativo. Altrimenti, potrete ignorarlo.

Aggiungiamo un pò di frameworks a quelli forniti in dotazione standard. Ci serviranno durante il corso del nostro applicativo. Se non sapete a che servono, non vi preoccupate. Ogni qualvolta ne useremo uno, verrà indicato.


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-04

I frameworks da aggiungere sono:

  • AVFoundation.framework (necessario per dialogare con le API audio/video)
  • QuartzCore.framework (necessario per dialogare con CALayer)


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-05

Modifichiamo opportunamente il file di *-Prefix.pch. In questo modo, ci eviteremo noiosi warning ed errori da parte del compilatore ed eviteremo di dover includere gli headers necessari in ogni classe che ne necessita l’uso.


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-06

La classe CAEmitterLayer

La classe CAEmitterLayer crea un emettitore di particelle CAEmitterCell. Queste particelle, sono disegnate attraverso il framework Quartz / Core Animation direttamente sul layer della UIView che ci ospita. Questo significa che qualunque oggetto che eredita da UIView, potrà ereditare i favolosi effetti grafici che potremo creare. Molto bene, mettiamoci al lavoro.

L’idea generale sarà quella di creare un “contenitore” CAEmitterLayer, aggiungendogli un numero variabile di “particelle” CAEmitterCell. Ogni cella produrrà particelle secondo la configurazione fornita. Come potete immaginare gli usi e gli scopi sono infiniti, tra cui quello dei fiocchi di neve.

Prima di tutto dobbiamo procurarci un’istanza valida di CAEmitterLayer. Se guardiamo la documentazione Apple, ci accorgeremo che la classe CAEmitterLayer eredita direttamente da CALayer. Questo significa che qualunque oggetto che dialoga con un CALayer, potrà essere utile per il nostro scopo. E’ proprio quello che cercavamo.

Creiamo una nuova classe che chiameremo SnowflakeParticleView e che eredita da UIView.


CAEmitterLayer-creare-un-generatore-di-neve-per-iphone-opengl-07

Modifichiamo l’header della classe, aggiungendo una variabile d’istanza che chiameremo snowflakeEmitter ed un’altra che farà riferimento all’istanza (singletone) del nostro AppDelegate.

#import 

@class AppDelegate;

@interface SnowflakeParticleView : UIView
{
    AppDelegate *appDelegate;
    CAEmitterLayer* snowflakeEmitter;
}

Nel metodo di init, provvederemo ad inizializzare le variabili dichiarate nell’interfaccia, così come indicato sotto.

- (id)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        //Create the emitter layer and retain it
        snowflakeEmitter = [[CAEmitterLayer layer] retain];
        appDelegate = (AppDelegate *)\
                      [[UIApplication sharedApplication] delegate];
    }
    return self;
}

In questo modo, tutta la classe SnowflakeParticleView condividerà la stessa istanza di snowflakeEmitter (il nostro emettitore di neve). Alla variabile appDelegate, invece, assegneremo il riferimento di memoria della classe singletone AppDelegate. Quest’ultima è importante perchè sarà al suo interno che dichiareremo il nostro player di musica…abbiamo detto che è Natale, giusto? Ricordiamoci di dichiarare ed implementare un metodo di dealloc per il nostro oggetto.

- (void)dealloc
{
    [snowflakeEmitter release];
    [super dealloc];
}

Gestione del tocco ed interazione con UIView

Ogni oggetto che eredita da UIView, può implementare tre metodi magici che gli permettono di prendere il controllo del tocco dell’utente (esistono anche altri modi per farlo ma in questa sede e per semplicità useremo questa tecnica). Ci basterà implementare la logica corrispondente ad ogni azione (inizio tocco, movimento, fine tocco) per fare in modo che tutto vada come desideriamo.

Partiamo dall’evento di inizio tocco (touchesBegan:withEvent). Quando toccate un oggetto che eredita da UIView, il suo UIResponder lancia un messaggio che, se correttamente implementato all’interno della nostra subclasse, permette di monitorare
e reagire al tocco.

Nel nostro caso, quello che dovremo fare sarà fare partire il nostro audioPlayer ed iniziare a sparare fiocchi di neve.

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if(![appDelegate.audioPlayer isPlaying])
        [appDelegate.audioPlayer play];
...

Il parametro touches, passato al metodo dall’evento di tocco, ci permette di ottenere informazioni importanti, quali ad esempio la posizione relativa del tocco sullo schermo. Da questo punto faremo partire il nostro generatore di neve.

...
CGPoint pt = [[touches anyObject] locationInView:self];
...

Infine, modifichiamo la posizione dell’emettitore snowflakeEmitter, facendola corrispondere con il punto di tocco sullo schermo. Variamo anche la sua modalità di emissione (emitterMode), lo shape usato per generare particelle (emitterShape) e la modalità di rendering usata dal motore Quartz (renderMode). Per i dettagli, vi invito a dare un occhiata alla documentazione Apple fornita di seria. Risulta molto precisa ed esaustiva.

...
    snowflakeEmitter.emitterPosition = pt;
    snowflakeEmitter.emitterMode = kCAEmitterLayerOutline;
    snowflakeEmitter.emitterShape = kCAEmitterLayerCircle;
    snowflakeEmitter.renderMode = kCAEmitterLayerAdditive;
    snowflakeEmitter.emitterSize = CGSizeMake(100 * multiplier, 0);
...

E’ la volta delle nostre particelle di neve. Creiamo una cella emettitrice di particelle e configuriamola secondo le nostre preferenze. Tra i parametri che possiamo modificare spiccano: birthRate per il numero di particelle generate in un range di tempo, lifeTime che indica il tempo di vita di ogni particella e velocity per la velocità.

...
    //Create the emitter cell
    CAEmitterCell* aSnowflake = [CAEmitterCell emitterCell];
    aSnowflake.birthRate = 200;
    aSnowflake.lifetime = 3.0;
    aSnowflake.lifetimeRange = 0.5;
    aSnowflake.velocity = 0.5*pt.x;
    aSnowflake.velocityRange = 0.5*pt.x;
    aSnowflake.emissionRange = 1.5*pt.x;
    aSnowflake.scaleSpeed = 0.1;
    aSnowflake.spin = 0.3;
    aSnowflake.emissionLatitude = 100.0;
    aSnowflake.yAcceleration = 150.0;
    aSnowflake.xAcceleration = 0.0;
    aSnowflake.emissionLongitude = 0.0;
    aSnowflake.color = [[UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.1] CGColor];
    aSnowflake.contents = (id)[[UIImage imageNamed:@"snow.png"] CGImage];
    [aSnowflake setName:@"snowflake"];
...

Possiamo creare quante celle vogliamo. L’importante è passare al nostro emettitore un oggetto NSArray, contentente tutte le celle create. Nel nostro caso, popoleremo l’array con un solo elemento. Vi invito a non abbondare con il numero di celle create.
Spesso è possibile ottenere lo stesso effetto che state cercando giocando con i parametri sopra e non con il numero di particelle create. La documentazione Apple, ancora una volta, sarà nostra amica.

...
    snowflakeEmitter.emitterCells = [NSArray arrayWithObject:aSnowflake];
    [self.layer addSublayer:snowflakeEmitter];
...

Ad ogni movimento del nostro dito, supponendo di non averlo tolto dallo schermo, viene invocato un’altro metodo. Anche questo, se implementato, ci fornisce di interagire con il movimento del nostro utente. Tutto quello che faremo sarà aggiornare la posizione del nostro emettitore (che, vi ricordo, contiene tutte le celle generatrici di particelle).

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint pt = [[touches anyObject] locationInView:self];
    snowflakeEmitter.emitterPosition = pt;
}

Analogamente, al rilascio del tocco, implementando il metodo sotto potremo effettuare le operazioni di blocco del nostro player musicale e della neve.

E’ interessante notare “come” blocchiamo la neve. Anzichè rilasciare l’oggetto snowflakeEmitter (che vi ricordo essere condiviso per tutta la durata di vita della nostra classe SnowFlakeParticleView), semplicemente ne configuriamo la grandezza dell’emettitore (immaginatevelo come la grandezza del tubo da cui esce la neve) a zero. Simultaneamente, ci adoperiamo per interrompere il flusso di emissione delle particelle, settando a zero la proprietà birthRate di ogni cella.

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if([appDelegate.audioPlayer isPlaying])
           [appDelegate.audioPlayer pause];

    for(CAEmitterCell *aSnowFlake in
        snowflakeEmitter.emitterCells) {
        aSnowFlake.birthRate = 0;
    }

    snowflakeEmitter.emitterSize = CGSizeZero;
}

Modifiche finali ed AppDelegate

Spostiamoci nel file AppDelegate.h e modifichiamolo come segue:

@interface AppDelegate : UIResponder  {
    AVAudioPlayer *audioPlayer;
}

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, retain) AVAudioPlayer *audioPlayer;
@end

La variabile di istanza audioPlayer, ci servirà per poter “rispescare” ed interagire con l’oggetto audioPlayer dall’interno della classe generatrice dei fiocchi di cui sopra.

Esercizio: Al posto dell’uso di una shared instance di AppDelegate, per indicizzare un’oggetto globale quale il lettore musicale, si potrebbe pensare di costruire una nuova classe singletone.

Spostiamoci nel file di implementazione AppDelegate.m e sintetizziamo la nuova variabile:

@synthesize audioPlayer;

ricordiamoci di aggiungere nella sezione di header la dichiarazione di import per la classe generatrice di neve creata sopra.

#import "SnowflakeParticleView.h"

Modifichiamo il metodo di didFinishLaunchingWithOptions come segue:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
*)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    self.window.backgroundColor = [UIColor colorWithRed:0.0/255.0
                                                  green:5.0/255.0
                                                   blue:113.0/255.0
                                                  alpha:1.0];

    NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"jingleBells"
                                                                        ofType:@"mp3"]];
    NSError *error;
    audioPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error] autorelease];
    audioPlayer.numberOfLoops = -1;
  
    if (audioPlayer == nil) NSLog(@"%@", [error description]);

    SnowflakeParticleView *contentView = [[SnowflakeParticleView alloc] init];

    [self.window addSubview:contentView];
    [contentView release];

    [self.window makeKeyAndVisible];
    return YES;
}

Se avete seguito tutti i passi, potete compilare e godervi il frutto della vostra fatica. Se non ci siete riusciti o siete stati impegnati nella lettura, potete scaricare lo zip con tutto il progetto compresso a questo indirizzo.

Grazie tante per l’attenzione dedicatami. Vi auguro un felice periodo di Natale ed uno straordinario anno nuovo, ricco di applicazioni ed idee entusiasmanti.

Costantino aka (valv`0)

Se avete problemi con il progetto presentato, questo è il link per scaricare il nostro esempio.


banner-allertasoglie-push-tre

Share this story:
  • tweet

Tags: CAEmitterCellCAEmitterLayerCore AnimationOpenGL iOSQuartz.coreTutorial Pratici

Recent Posts

  • Parte il percorso programmatori iOS in Swift su devACADEMY.it

    20 Dicembre 2017 - 0 Comment
  • Android, crittografare dati velocemente con Encryption

    24 Settembre 2018 - 0 Comment
  • Sql2o, accesso immediato ai database tramite Java

    3 Settembre 2018 - 0 Comment
  • Okio, libreria per ottimizzare l’input/output in Java

    27 Agosto 2018 - 0 Comment

Related Posts

  • T#112 – Animazioni ed effetti per le nostre applicazioni iPhone e iPad

    31 Gennaio 2013 - 2 Comments
  • T#035 – Creare uno screenshot a runtime

    19 Aprile 2010 - 9 Comments

Author Description

7 Responses to “T#104 – CAEmitterLayer: Creiamo un simpatico generatore di fiocchi di neve con il nostro iPhone”

  1. 15 Dicembre 2011

    Francesco M.

    Buonasera, ho compilato il progetto ed eseguito con il simulatore ma ho sempre schermo blu e niente fiocchi…dove sbaglio?..Grazie

  2. 15 Dicembre 2011

    Byteros

    A me il VS progetto non funziona, compila tutto bene ma il simulatore mi mostra una schermata blu e nient’altro 🙁

  3. 15 Dicembre 2011

    Ragazzetto

    Grazie Costantino !
    Tutorial fantastico !
    Concordo però con Byteros il progetto restituisce nella consolle di debug questo risultato sia su simulatore che su idevice :
    SnowFlakes[1113:707] Applications are expected to have a root view controller at the end of application launch

    Consigli per vederlo all opera ?

    Grazie tante !

  4. 16 Dicembre 2011

    Gabriele Trabucco

    Eh si il progetto non va proprio!
    Ci sono diversi errori. Per farlo funzionare …

    In AppDelegate togliere l’autorelease all’audioPlayer e inserire il suo release nel dealloc

    creare una sottoclasse di UIViewController e instanziare un suo oggetto in application:didFinishLaunchingWithOptions:

    poi prima del makeKeyAndVisible inserire

    [self.window setRootViewController:snowController];

    all’interno del viewDidLoad del nuovo snowController inserire

    SnowflakeParticleView *contentView = [[SnowflakeParticleView alloc] initWithFrame:self.view.frame];
    [self.view addSubview:contentView];

    e runnare. Trascinando il dito sullo schermo vedrete del fumo stile Lost piuttosto che la neve 🙂

    Ad ogni modo ottimo perché non sapevo dell’esistenza di questa classe.
    Grazie mille!

  5. 16 Dicembre 2011

    valvoline

    Ciao Gabriele! 🙂

    Ottimo commento…era il secondo esercizio sull’articolo: RENDERE il progetto funzionante! 🙂

    Buona programmazione a tutti

    —
    c.

  6. 16 Dicembre 2011

    valvoline

    Ciao,

    Alcune precisazioni sull’ultimo commento di Gabriele. Siamo qui per imparare, quindi mi sembra giusto fare chiarezza.

    1) Eliminazione dell’autorelease su AudioPlayer.
    Non e’ un errore mettere in autorelease l’audioplayer. L’inesattezza nel codice è da ricercare nella modalità di accesso alla variabile d’istanza audioPlayer ( e nessuno se ne è accorto…sigh 🙁 )

    Come avrete notato, la property di audioPlayer è flaggata come (nonatomic, retain). Questo ci permette di giocare con autorelease come ho fatto nel codice, a patto di accedere alla variabile con l’accessor corretto. Se si effettua la seguente modifica sul codice:


    NSError *error;
    self.audioPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error] autorelease];
    self.audioPlayer.numberOfLoops = -1;
    NSLog(@"retain counter: %d", self.audioPlayer.retainCount);

    Tutto inizierà a funzionare correttamente. Se invece lasciamo tutto com’era:


    NSError *error;
    audioPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error] autorelease];
    audioPlayer.numberOfLoops = -1;
    NSLog(@"retain counter: %d", audioPlayer.retainCount);

    Il retain counter del nostro audioPlayer sarà pari ad uno, con un conseguente decremento (e quindi rilascio) della stessa all’uscita del metodo. Il motivo di questa stramberia è molto semplice.

    Accedendo alla variabile in maniera diretta, stiamo lavorando su di essa in maniera diretta. Utilizzando, invece, la notazione self. faremo uso dei metodi setter e getter (creati dal nostro synthetize). Questo ci permette di usare a nostro favore l’opzione retain dichiarata che, di fatto, trattiene e porta a due il valore del nostro retain counter.

    2) Applications are expected to have a root view controller at the end of application launch

    Da iOS5 in poi, è necessario specificare un controllore root all’interno del metodo didFinishLaunching. Questo non ci permette piu’ di costruire direttamente sulla window, come era fatto nel progetto. La risposta di Gabriele sulla creazione di un oggetto ViewController, atto a contenere il nostro SnowflakeParticleView è ottima.

    Due sole precisazioni: nella soluzione suggerita, si aggiunge ( addSubview ) l’oggetto SnowParticleView nella viewDidLoad.

    Non è necessario aggiungere come subview alla view principale e non è molto performante CREARE la vista nel metodo viewDidLaod (che dovrebbe, appunto, essere chiamato quando la view è stata creata).

    Se abbiamo deciso di costruire programmaticamente la view, allora sarebbe opportuno spostare le inizializzazioni all’interno dell’apposito metodo loadView:


    // Implement loadView to create a view hierarchy programmatically, without using a nib.
    - (void)loadView
    {
    SnowflakeParticleView *contentView = [[SnowflakeParticleView alloc] init];
    self.view = contentView;
    [contentView release];
    }

    Grazie tante per i consigli. Il progetto allegato all’articolo è ora rivisto e corretto.

  7. 29 Dicembre 2011

    daniela

    Scusate ma se si volesse far girare con una versione precedente dell’IOs 5?
    Mi da errori di metodi sconosciuti ?

Leave a Reply

Your email address will not be published. Required fields are marked *


*
*

Corso online di programmazione android e java

SEZIONI

  • Android
  • Comunicazioni
  • Contest
  • Corsi ed Eventi
  • Corso completo di C
  • Corso programmazione videogiochi
  • Framework
  • Grafica e Design
  • Guida rapida alla programmazione Cocoa Touch
  • Guide Teoriche
  • Guide varie
  • iPad
  • Le nostre applicazioni
  • Libri e manuali
  • Materiale OpenSource
  • News
  • Pillole di C++
  • Progetti completi
  • Risorse utili
  • Strumenti di Sviluppo
  • Swift
  • Tips & Tricks
  • Tutorial Pratici
  • Video Tutorial
  • Windows Phone

Siti Amici

  • Adrirobot
  • Allmobileworld
  • Apple Notizie
  • Apple Tribù
  • Avvocato360
  • Blog informatico 360°
  • bubi devs
  • fotogriPhone
  • GiovaTech
  • iApp-Mac
  • iOS Developer Program
  • iPodMania
  • MelaRumors
  • Meritocracy
  • SoloTablet
  • TecnoUser
  • Privacy & Cookie Policy
©2009-2018 devAPP - All Rights Reserved | Contattaci
devAPP.it è un progetto di DEVAPP S.R.L. - Web & Mobile Agency di Torino
Str. Volpiano, 54 - 10040 Leini (TO) - C.F. e P.IVA 11263180017 - REA TO1199665 - Cap. Soc. € 10.000,00 i.v.

devACADEMY.it

Vuoi imparare a programmare?

Iscriviti e accedi a TUTTI i corsi con un’unica iscrizione.
Oltre 70 corsi e migliaia di videolezioni online e in italiano a tua disposizione.

ISCRIVITI SUBITO