Category Archives: 2D Grafiikka

CGAffineTransformation ja UI elementit

Affiinit kuvaukset ovat käytännössä pakon sanelemina tuttuja kaikille 3D grafiikkaohjelmointia harrastaneille mutta ne ovat aivan yhtä hyodyllisiä (joskaan eivät samalla tavalla edellytys) tasossa toimiessa. iOS:n Cocoa Touch tarjoaa useita tapoja manipuloida UIView:ien (jokaisen näkyvän UI widgetin kantaluokka) paikkaa; framen sijainnin muuttaminen taitaa olla päivittäisessa käytössä yleisin tapa ja se onkin riittävä ja oikea valinta suurimpaan osaan tapauksia.Mutta jos elementtejä halutaan skaalata tai kiertää (rotate), tarvitaan muita konsteja.

CGAffineTransformation esittää 3×3 matriisia jolla tason (2D) affiinit kuvaukset voidaan esittää. UIView luokalla on transformation niminen property, jonka animoitavuus tekee affiineista kuvauksista erittäin voimakkaan työkalun. Esimerkiksi UIView:n siirtäminen koordinaatteihin (tx,ty) tapahtuu CGAffineTransformation:n avulla seuraavasti:

CGAffineTransform transform = CGAffineTransformMakeTranslation(tx, ty);
view.transform = transform;

Jos on tottunut siirtelemään elementtejä muokkaamalla framen sijaintia, kannattaa huomioida että CGAffineTransformation:lla tehtävät siirrot (translation) kohdistuvat elementin keskipisteeseen eivätkä esim. sen vasempaan yläkulmaan.

Kuvauksia voi (ja pitää) yhdistää; usean operaation lopputulos mahtuu yhteen samaan kuvausmatriisiin sen ominaisuuksien vuoksi. Eri kuvausmatriisit kerrotaan yhteen oikeassa järjestyksessä ja lopputuloksella “kuvataan” (transform, v.)  elementti uuteen sijaintiin, skaalaan ja/tai orientaatioon. Esimerkiksi voidaan yhdistää translate – scale – rotate:

CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, tx, ty);
transform = CGAffineTransformScale(transform, kScaleFactor, kScaleFactor);
transform = CGAffineTransformRotate(transform, kRotationAngle * M_PI / 180.0);
view.transform = transform;

Kuten aina affiinien kuvausten kanssa, on huomioitava operaatioiden järjestys; translaatio ensin, rotaatio sitten -järjestys siirtää kappaletta origosta ja sen jälkeen kiertää sitä kehää pitkin; rotaatio ensin, translaatio sitten -järjestys kiertää kappaletta oman keskipisteensä ympäri ja sen jälkeen siirtää kappaletta uuteen paikkaansa.

Esimerkkitoteutus

Ohessa yksinkertainen single-view sovellus joka pyrkii esittelemään yllä läpikäydyn asian joustavuuden ja voiman visualisoimalla ristinollaruudukon ja 5 kpl kumpaakin pelimerkkia O / X; koskettamalla pelimerkkiä kyseinen merkki tarttuu mukaan ja sille tehdään pieni skaalaus / rotaatio ihan vain demonstraation takia. Kaikki kappaleen manipulaatio tehdään CGAffineTransformation:lla.

Esimerkkisovelluksen ruutukaappaus
Esimerkkisovelluksen ruutukaappaus

Esimerkkisovelluksen lähdekoodi Xcode projektina (42kB)

 

 

 

 

 

 

ps. jos termi “affiini kuvaus” tuntuu kryptiseltä niin käytännössä sen määritelmä on että kys. operaatiossa “kuvattava” eli transformoitava kappale ei muuta mittasuhteitaan tai muotoaan; neliö pysyy neliönä niin sitä siirrettäessä, skaalatessa kuin kierrettäessäkin.

Lisää aiheesta

 

Liikkuva 2D-grafiikka Androidilla

Ensimmäinen asia, mitä lähes jokainen aloitteleva ohjelmoija haluaa tehdä on oma peli. Tämän toteuttamiseen tarvitaan lähes välttämättä liikkuvien grafiikkojen piirtämistä. Tässä artikkelissa esitetään Androidin 2D-grafiikkojen piirtäminen kankaalle. Käymme myös läpi hiukan yksinkertaista säikeiden käyttöä ja säieturvallisuutta.

Androidille 2D-grafiikkojen piirtämiseen tarvitaan näkymä, jolle voi piirtää. Toteutamme tässä esimerkissä oman versiomme SurfaceView:stä, jolle piirtäminen tapahtuu käytännössä antamalla SurfaceView:lle kankaan onDraw()-metodissa.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    starList = new ArrayList<StarFallActivity.Star>();

    setContentView(new StarSurface(this));
}

class StarSurface extends SurfaceView implements SurfaceHolder.Callback {
    private GameThread gameThread;
    private GraphicThread graphicThread;

public StarSurface(Context context) {
    super(context);
    getHolder().addCallback(this);
    gameThread = new GameThread(this);
    graphicThread = new GraphicThread(getHolder(), this);
}

@Override
public void onDraw(Canvas canvas) {
    canvas.drawColor(Color.BLACK);
    synchronized (starList) {
        for(Star star : starList) {
            canvas.drawBitmap(star.getImage(), star.getX(), star.getY(), null);
        }
    }
}
...

Pidämme kirjaa rajoittamattomasta määrästä putoavia objekteja ja piirrämme ne kankaalle niiden sisällään pitämän sijainnin muukaan onDraw()-metodissa. Resursseissa olevan kuvan piirtäminen valmiista kuvaresurssista ei vaadi, kuin resurssin bittikartaksi muuttamisen ja X- ja Y-koordinaatit, mihin kuva pintanäkymässä tulee.

BitmapFactory.decodeResource(getResources(), R.drawable.icon);

Säikeet ja säieturvallisuus

Pidämme objektien liikuttamisen ja ruudunpäivityksen erillisissä säikeissä ja laitamme säikeet odottamaan, kun niitä ei tarvita. Säikeiden käytössä täytyy myös ottaa huomioon samanaikaisten operaatioiden turvallisuus. Koska pidämme liikuvat objektit yhdessä listassa täytyy lista asettaa synkronoiduksi, niin että siihen voi kohdistua vain yksi operaatio kerrallaan. Tämä täytyy tehdä jokaisessa paikassa, missä listaa käsitellään. Esimerkiksi pelin osia liikuttelevassa säikeessä:

synchronized (starList) {
    for (Iterator<Star> iterator = starList.iterator(); iterator.hasNext();) {
        Star star = iterator.next();
        star.move();
        if(star.getY() >= maxHeight) {
            iterator.remove();
        }
    }
}

Koska poistamme listasta putoavan tähden, jos se menee ruudun ulkopuolelle joudumme käyttämään iteraattoria, koska listasta poistaminen sen läpikäynnin aikana ei suoraan ole mahdollista.

Säikeissä tarvitsee pääasiassa vain metodit säikeen käynnissä olon asettamiseen ja run()-metodin oma toteutus, missä säikeen suoritusaikainen toiminta tapahtuu. Sijettä ei tule tappaa stop()-metodilla, sillä se voi jättää ohjelmistosi outoon tilaan. Tämän sijasta tulisi käyttää run()-metodissa while lausetta joka toistuu niin kauan, kuin säikeen sisäinen while ehto muuttuu epätodeksi ja run()-metodi pääsee suorittamaan loppuun. Säikeet joiden run()-metodi on päässyt suoriutumaan kokonaan tapetaan automaattisesti järjestelmän toimesta.

Jos et halua säikeesi vievän kaikkea prosessointitehoa aseta säie jokaisen while luupin lopuksi odottamaan jonkin aikaa. Piirtopintaa ei esimerkiksi kannata päivittää useampaa, kuin 60 kertaa sekunnissa ja tämäkin on yleensä liikaa. Säikeen voi myös pakottaa käynnistymään kutsumalla sille notify()-metodia.

Lataa esimerkki tästä.
Voit nyt tehdä esimerkiksi oman versiosi kitarasankarista ja pudottaa ruudun yläpäästä kuvia ja tarkastella osuuko käyttäjä niihin ennen, kuin ne putoavat ulos ruudulta.