Multiplatform (Windows, Linux, Mac OS X, Android) uz pomoć FPC/Lazarus

Multiplatform (Windows, Linux, Mac OS X, Android) uz pomoć FPC/Lazarus

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Konačno i nastavak teme FPC za Android... kako napisati aplikaciju koju, uz minimalno napora, možemo iskompajlirati za Windows, Linux, Mac OS X i Android (nažalost, za iPad/iPhone je potrebno na poseban način iskompajlirati FPC pa ću o tome pisati neki drugi put). Pre nego što počnete da čitate ovo, bilo bi dobro da već znate da pravite jednostavne igre za Android i bar jedan desktop OS. Ako želite da iskompajlirate program za Android, treba vam i najnoviji Android SDK, Android NDK i Eclipse (minimalna verzija Androida za koju će raditi OpenGL aplikacije kompajlirane iz FPC je 1.6).

Za početak moramo obratiti pažnju na sve što se radi drugačije na različitim platformama. Svaka platforma ima svoj sistem za kreiranje prozora, za obradu poruka i crtanje. Ono što će biti isto za sve platforme je logika koja će upravljati tokom igre. Da ne bi morali da pišemo brdo koda, iskoristićemo SDL biblioteku za rad sa prozorima i OpenGL(ES) za crtanje.

Počećemo pisanjem koda koji će biti jednak za sve platforme, i taj kod će biti podeljen na 4 dela:

Inicijalizacija
Obrada poruke o promeni veličine prozora
Animacija
Crtanje


Inicijalizacija će biti prilično jednostavna. Pošto će kod za kreiranje prozora pripremiti i OpenGL, nama ovde ostaje samo postavljanje osnovnih parametara za crtanje:
procedure Init; begin   glDisable(GL_LIGHTING);   glDisable(GL_CULL_FACE); end;

Obrada poruke o promeni veličine prozora je takođe jednostavna. Jedini trik je da za OpenGL (desktop plathorme) zovemo funkciju glFrustum, a za OpenGL ES glFrustumf:
procedure Resize(Width, Height: Integer); var   X, Y: Single; begin   glViewport(0, 0, Width, Height);   glMatrixMode(GL_PROJECTION);   glLoadIdentity;   Y := Sin(45 * pi / 360) / Cos(45 * pi / 360);   X := Y * (Width / Height); {$IFDEF CPUARM}   glFrustumf(-X, X, -Y, Y, 1, 100); {$ELSE}   glFrustum(-X, X, -Y, Y, 1, 100); {$ENDIF}   glMatrixMode(GL_MODELVIEW);   glLoadIdentity;   glTranslatef(0, 0, -5); end;

Procedura za animaciju će kao parametar dobiti vreme koje je prošlo od zadnjeg crtanja i samo će rotirati celu scenu:
procedure Update(DeltaTime: Integer); begin   glRotatef(DeltaTime / 10, 0, 1, 0); end;

Crtanje je u našem primeru takođe jednostavno... samo jedan trouglić:
var   Vertices: array[0..20] of Single = (     0, 1, 0, 1, 0, 0, 1,     -1, -1, 0, 0, 1, 0, 1,     1, -1, 0, 0, 0, 1, 1   ); procedure Render; begin   glClear(GL_COLOR_BUFFER_BIT);   glEnableClientState(GL_VERTEX_ARRAY);   glVertexPointer(3, GL_FLOAT, 28, @Vertices[0]);   glEnableClientState(GL_COLOR_ARRAY);   glColorPointer(4, GL_FLOAT, 28, @Vertices[3]);   glDrawArrays(GL_TRIANGLES, 0, 3);   glDisableClientState(GL_VERTEX_ARRAY);   glDisableClientState(GL_COLOR_ARRAY); end;

Sada se bacamo na kod koji će na desktop platformama kreirati prozor i vrteti glavnu petlju. Kao što rekoh, koristićemo SDL da ne bi morali da pišemo poseban kod za svaku platformu:
program GLDemo; {$R *.res} uses   sysutils, sdl, GLApp; var   Window: PSDL_Surface;   Event: TSDL_Event;   Running: Boolean;   CurrentTime, NewTime: Cardinal; begin   try     if SDL_Init(SDL_INIT_VIDEO) <> 0 then       raise Exception.Create('Failed to initialize SDL: ' + SDL_GetError);     SDL_putenv('SDL_VIDEO_CENTERED=1');     SDL_ShowCursor(SDL_DISABLE);     SDL_WM_SetCaption('GLDemo', 'GLDemo');     Window := SDL_SetVideoMode(800, 600, 32, SDL_DOUBLEBUF or SDL_OPENGL or SDL_RESIZABLE);     if Window = nil then       raise Exception.Create('Failed to set video mode: ' + SDL_GetError);     Init;     Resize(800, 600);     Running := True;     CurrentTime := SDL_GetTicks;     while Running do     begin       while SDL_PollEvent(@Event) <> 0 do         if Event.type_ = SDL_QUITEV then           Running := False         else if Event.type_ = SDL_VIDEORESIZE then           Resize(Event.resize.w, Event.resize.h);       NewTime := SDL_GetTicks;       Update(NewTime - CurrentTime);       CurrentTime := NewTime;       Render;       SDL_GL_SwapBuffers;     end;   finally     SDL_Quit;   end; end.

Ova aplikacija će pokrenuti SDL i kreirati prozor. Odmah posle kreiranja prozora, poziva Init funkciju koju smo ranije definisali, a zatim i Resize funkciju kako bi program pravilno postavio parametre za crtanje. Zatim ulazi u glavnu petlju u kojoj poziva Update i Render funkcije koje se brinu o crtanju. U slučaju da se veličina prozora ponovo promeni, zove se funkcija Resize i prosleđuju se nove dimenzije.

Ništa specijalno, ali zahvaljujući tome što smo koristili SDL i OpenGL, ova aplikacija već sada može da se iskompalira i pokrene na Windowsu, Linuxu i Macu.

Za sada je sve bilo prilično standardno, ali se za Android moramo malo više pomučiti. Problem je u tome što deo aplikacije za kreiranje prozora moramo napisati u Javi, što za sobom povlači i to da naš kod za logiku, kojeg smo ranije napisali, moramo kompajlirati kao biblioteku, a ne kao izvršni program. Da stvar bude još gora, funckije u biblioteci moraju biti napisane na tačno definisan način kako bi Java znala da ih pozove.

Da krenemo s Java delom... on se piše na manje-više isti način kao kad bi želeli da koristite OpenGL u Javi:
package srki.android; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.content.Context; import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView.Renderer; import android.os.Bundle; import android.os.SystemClock; import android.view.Window; import android.view.WindowManager; public class GLDemo extends Activity {    private SceneView glView = null;        private native void NativeInit();    private native void NativeResize(int width, int height);    private native void NativeUpdate(int deltaTime);    private native void NativeRender();     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         this.requestWindowFeature(Window.FEATURE_NO_TITLE);         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);         glView = new SceneView(this);         setContentView(glView);    System.loadLibrary("gldemo");     }         @Override     protected void onPause() {         super.onPause();         glView.onPause();     }     @Override     protected void onResume() {         super.onResume();         glView.onResume();     }     public class SceneView extends GLSurfaceView {                public SceneView(Context context) {             super(context);             setRenderer(new SceneRenderer());         }     }         public class SceneRenderer implements Renderer {                private long currentTime, newTime;        @Override        public void onDrawFrame(GL10 gl) {           newTime = SystemClock.elapsedRealtime();           NativeUpdate((int)(newTime - currentTime));           currentTime = newTime;           NativeRender();        }        @Override        public void onSurfaceChanged(GL10 gl, int width, int height) {           NativeResize(width, height);        }        @Override        public void onSurfaceCreated(GL10 gl, EGLConfig config) {           currentTime = SystemClock.elapsedRealtime();           NativeInit();        }     } }

Na standardan način kreiramo Activity, GLSurfaceView i Renderer... isto kao da bi posle toga krenuli sa standardnim crtanjem u Javi... razlika je u tome, da osim standardnih stvari pozivamo i System.loadLibrary("gldemo"). Ta linija će učitati biblioteku libgldemo.so i iz nje pročitati native funkcije koje smo definisali na početku koda. Imena tih funkcija su mogle biti iste kao i u kodu za logiku (bez Native prefiksa), ali dodavanjem prefiksa olakšavamo kasnije popravljanje koda, jer na osnovu imena znamo da li je funkcija definisana u Javi ili u nekoj biblioteci. Kada su funkcije pročitane iz biblioteke, možemo ih koristiti kao da smo ih definisali u kodu u Javi. Ovaj mali programčić poziva NativeInit čim je OpenGL ES spreman za rad, NativeResize prilikom promene veličine prozora, i NativeUpdate i NativeRender kad god je potrebno da se scena nacrta na ekran.

Stižemo i do zadnjeg dela... biblioteka koju će Java aplikacija koristiti mora biti napisana u skladu sa JNI pravilima: http://java.sun.com/docs/books/jni/html/jniTOC.html
library GLDemoAndroid; {$linklib c} uses   jni, GLApp; function JNI_OnLoad(VM: PJavaVM; Reserved: Pointer): jint; cdecl; begin   Result := JNI_VERSION_1_4; end; function JNI_OnUnload(VM: PJavaVM; Reserved: Pointer): jint; cdecl; begin   Result := 0; end; procedure Java_srki_android_GLDemo_NativeInit(JNIEnv: PJNIEnv; Obj: jobject); cdecl; begin   Init; end; procedure Java_srki_android_GLDemo_NativeResize(JNIEnv: PJNIEnv; Obj: jobject; Width, Height: jint); cdecl; begin   Resize(Width, Height); end; procedure Java_srki_android_GLDemo_NativeUpdate(JNIEnv: PJNIEnv; Obj: jobject; DeltaTime: jint); cdecl; begin   Update(DeltaTime); end; procedure Java_srki_android_GLDemo_NativeRender(JNIEnv: PJNIEnv; Obj: jobject); cdecl; begin   Render; end; exports   JNI_OnLoad,   JNI_OnUnload,   Java_srki_android_GLDemo_NativeInit,   Java_srki_android_GLDemo_NativeResize,   Java_srki_android_GLDemo_NativeUpdate,   Java_srki_android_GLDemo_NativeRender; end.

Kratko objašnjenje za one koje mrzi da pročitaju dokumentaciju... biblioteka mora imati najmanje 2 funkcije i to JNI_OnLoad i JNI_OnUnload. JNI_OnLoad vraća verziju JNI sa kojom biblioteka zna da radi. Preko parametara te funckije je moguće doći i do funkcija koje su definisane u Java kodu (koga to zanima, neka pročita dokumentaciju). JNI_OnUnload funkcija u suštini ne služi ničemu... u dokumentaciji piše da se ona poziva kada JNI završi sve što ima s bibliotekom, ali pošto java koristi GC, ne zna se kad će tačno (i da li će) biti pozvana.

Funkcije koje nudimo javi moraju imati sledeći oblik Java_ImePaketa_ImeKlase_ImeFunkcije. ImePaketa je ime Java paketa u kojem je definisana klasa u kojoj su upisane definicije native funkcija, i umesto tački se koriste donje crte. Pošto je naša Java aplikacija u paketu srki.android, a klasa se zove GLDemo sve funkcije počinju sa Java_srki_android_GLDemo_. Prva dva parametra su obavezna i služe za integraciju sa JNI, a posle njih slede parametri koje želimo da prosleđujemo našim funkcijama. Kao što se vidi iz koda, JNI funkcije jednostavno zovu funkcije koje smo prethodno definisali, i koje koristimo i u desktop verziji aplikacije.

Par bitnih stvari na koje treba obratiti pažnju prilikom kompajliranja ove biblioteke: treba izabrati Linux u polju Target OS, i arm u polju Target CPU family; obavezno postaviti put do Android NDK biblioteka; u slučaju da Java aplikacija dobije grešku prilikom učitavanja biblioteke, staviti optimization level na 1 ili 0.

Kada je biblioteka iskompajlirana, potrebno ju je postaviti na pravo mesto kako bi Java kod mogao da ga nađe. Biblioteka se mora nalaziti u direktorijumu: ImeProjekta/libs/armeabi. U slučaju da niste sigurni, pogledajte Android projekat na kraju ovog teksta i sve će vam biti jasno.

I, to je to... videli smo da kreiranje prozora mora da se napiše za svaku platformu posebno, ili da se koristi neka biblioteka koja može da nam olakša posao (SDL, SFML, Allegro, itd...), ali samu logiku programa/igre pišemo samo jednom.

Evo i par sličica sa svih platformi:
Android


Linux


Mac OS X


Windows


Kod možete preuzeti ovde: https://www.mycity.rs/must-login.png
Eclipse projekat za Android možete preuzeti ovde: https://www.mycity.rs/must-login.png



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
offline
  • Més que un club
  • Glavni vokal @ Harpun
  • Pridružio: 27 Feb 2009
  • Poruke: 3898
  • Gde živiš: Novi Sad,Klisa

Ima li kakvih novosti vezanih za iOS developing pod ovim okruzenjem (na winu naravno) ? Smile



offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Za iOS ti treba Mac OS X, ali sam nažalost ostao bez svog, a u virtualnoj mašini je pravo mučenje programirati za iOS, tako da i dalje nema novosti.

Ko je trenutno na forumu
 

Ukupno su 1387 korisnika na forumu :: 58 registrovanih, 7 sakrivenih i 1322 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 3466 - dana 01 Jun 2021 17:07

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: 357magnum, Aleksandar Tomić, babaroga, bladesu, Boris Bosiljčić, Boris90, botta, brundo65, ccoogg123, Denaya, doktor1964, Dorcolac, draganca, dragoljub11987, dule10savic, Faki-Valjevo, GandorCC, goxin, hologram, ikan, ivicasimo, JimmyNapoli, Joco Skljoco, JOntra, jukeboxer, kalens021, kokodakalo, Kubovac, kunktator, kybonacci, mercedesamg, Mercury, mile23, MiroslavD, mrvica78, nemkea71, nikoladim, oganj123, opt1, Panter, Parker, Romibrat, rovac, ruma, ruso, Shinobi, Sirius, Sićko, slonic_tonic, solic, SR-3m, vathra, VJ, Vladko, vukovi, wizzardone, Wrangler, Zimbabwe