venerdì 3 febbraio 2012

Android: impostare un context grafico OpenGL ES

E così vuoi fare un gioco per Android.
Benvenuto compagno di viaggio.
Non hai ancora deciso se sarà un 3D o un 2D, ma tanto non ti interessa, dato che non ti piegherai ad usare un Canvas o una semplice View per disegnare, tu vuoi codare the hard way, vuoi la potenza dell'accelerazione hardware, vuoi OpenGl ES.

E allora eccoci qui. La prima cosa da fare è capire come si imposta il contesto grafico, e arrivare alla mitica schermata monocolore. Da lì in avanti, lo sappiamo, è tutta in discesa... (chi ha detto caduta libera?)

Suppongo tu sappia già come funziona una applicazione Android, e se non lo sai, beh, sappilo. Mi riferisco a inezie tipo il concetto di Activity (non ti servirà comunque molto altro).

Quindi, il nostro progetto partirà con una Activity vuota ma funzionante. Per impostare il nostro contesto grafico ci occorrono essenzialmente due cose:

  1. Una istanza della classe GLSurfaceView
  2. Una implementazione dell'interfaccia GLSurfaceView.Renderer

La prima ce la fornisce il framework, ma in un progetto reale vorremo probabilmente ereditare da quella base per accedere alle notifiche dell'input utente e, in generale, avere maggiore controllo sulla vita dell'applicazione. Per ora possiamo accontentarci, ad ogni modo, di usarla così come è.

La seconda necessita invece di un po' di codice. GLSurfaceView.Renderer è l'interfaccia dell'oggetto che si occupa di creare il contesto opengl, gestirne i cambiamenti (risoluzione dello schermo) e disegnare il frame. Implementandola, dovremo scrivere il codice per i metodi:

public void onDrawFrame(GL10 gl);
public void onSurfaceChanged(GL10 gl, int width, int height);
public void onSurfaceCreated(GL10 gl, EGLConfig conf);

Dove GL10 è l'oggetto wrapper sulle API OpenGL (1.0).

Ecco una semplice implementazione:

public class MyRenderer implements Renderer {
 
 @Override
 public void onDrawFrame(GL10 gl) {
  gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
  
  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glLoadIdentity();
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int w, int h) {
  gl.glViewport(0, 0, w, h);
  gl.glMatrixMode(GL10.GL_PROJECTION);
  gl.glLoadIdentity();
  GLU.gluPerspective(gl, 45f, (float)w / (float)h, 0.1f, 450f);
  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glLoadIdentity();
 }

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig conf) {
  gl.glShadeModel(GL10.GL_SMOOTH);
  gl.glClearDepthf(1.0f);
  gl.glEnable(GL10.GL_DEPTH_TEST);
  gl.glDepthFunc(GL10.GL_LEQUAL);
  gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
  gl.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
 }

}

Per un gioco 2D, naturalmente, imposteremo la GL_PROJECTION con una glOrtho, o gluOrtho2D... insomma, OpenGL rimane sempre lei.

Il main, oops, scusate, la OnCreate dell'Activity, resta banale:

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyRenderer renderer = new MyRenderer();

        GLSurfaceView surface = new GLSurfaceView(this);
        surface.setRenderer(renderer);

        setContentView(surface);
}

Tutto quel che facciamo è istanziare il Renderer e la GLSurfaceView e passare a quest'ultima la nostra implementazione, quindi assegnare la View all'Activity.

E adesso via a scrivere il vostro gioco!