はじめに ― 2013/11/01
ブログなんてものを始めるにあたって、
好きなときに、好きなことを、好き勝手に書いていこうとは思うけど、
すぐにネタが尽きてしまうのもなんなので、
何かしら芯となるものを持とうと、Android NDKのプログラミングについて書いていくことにしました。
巷にはすでにNDKによるサンプルもあふれていて今更なんですが、個々の情報が分かれている感じもしますので、そのへんをまとめて1つのプロジェクトにしていこうかと思います。のんびりと。
好きなときに、好きなことを、好き勝手に書いていこうとは思うけど、
すぐにネタが尽きてしまうのもなんなので、
何かしら芯となるものを持とうと、Android NDKのプログラミングについて書いていくことにしました。
巷にはすでにNDKによるサンプルもあふれていて今更なんですが、個々の情報が分かれている感じもしますので、そのへんをまとめて1つのプロジェクトにしていこうかと思います。のんびりと。
まずはメインループ ― 2013/11/02
前回、テーマを「Android NDKのプログラミング」としましたが、これではちょっと曖昧なのでとりあえず目標を「2Dゲームの作成」として、必要なものを順番に作っていくことにします。
そこで一番初めの今回は、
・初期化
・一定間隔でメインの処理
という全体の流れ(だけ)を作成します。
それでは、なにはなくともアクティビティ
BaseActivity.java
アクティビティのonCreateでサーフェスビューとレンダラーを作成するとか、System.loadLibraryでnative側のプログラムを読み込むとかいろいろありますが、この辺はNDKのプロラムでは共通事項なので説明は省きます。シンプルに作っているので、見れば大体わかると思います。
さて、プログラムの流れを追っていくと、
BaseActivityの onCreate → onResume
BaseViewの onResume
BaseRendererの onSurfaceCreated → onSurfaceChanged
と進みます。
ここでまず、nativeの関数 initNativeを呼んでnative側を初期化しておきます。
次に、「一定間隔でメインの処理」を行うための設定をします。
ScheduledExecutorServiceのメソッド
ScheduledFuture<?> scheduleAtFixedRate(
Runnable command,
long initialDelay,
long period,
TimeUnit unit)
時間単位 unitで、initialDelay後から delay毎にタスク commandを実行します。
このプログラムでは、
・1000/30ms毎に
・surface_view.requestRender()を実行する
となります。
BaseViewのコンストラクタで setRenderMode(RENDERMODE_WHEN_DIRTY)としているので、surface_viewの requestRender()により rendererの onDrawFrame()が呼ばれます。
よって、1000/ms毎に、nativeの関数 updateNativeが呼ばれることになります。
それでは、native側のプログラム
native.cpp
今回はログを表示するだけで、何もしません。
なお、初期化処理といいつつ initNativeは起動時だけではなくサスペンドからのレジューム時などにも呼ばれますので注意してください。
プロジェクト一式は、こちら。
そこで一番初めの今回は、
・初期化
・一定間隔でメインの処理
という全体の流れ(だけ)を作成します。
それでは、なにはなくともアクティビティ
BaseActivity.java
package sys; import android.app.Activity; import android.content.Context; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.util.Log; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; /******************** アクティビティ ********************/ public class BaseActivity extends Activity { static { System.loadLibrary("native"); } private BaseView surface_view; // ビュー private BaseRenderer renderer; // レンダラー private ScheduledExecutorService executor; // 定期実行管理 private ScheduledFuture<?> future; public native void initNative(int _w, int _h); // native部初期化 public native boolean updateNative(); // native部稼働 /********** 開始 **********/ @Override protected void onCreate(Bundle _savedInstanceState) { super.onCreate(_savedInstanceState); renderer = new BaseRenderer(); // レンダラー作成 surface_view = new BaseView(getApplication(), renderer); // ビュー作成 setContentView(surface_view); executor = Executors.newSingleThreadScheduledExecutor(); // 定期実行管理 } /********** 終了 **********/ @Override protected void onDestroy() { super.onDestroy(); executor.shutdown(); } /************** 一時停止 **************/ @Override protected void onPause() { super.onPause(); if ( future != null ) { // 定期実行停止 future.cancel(false); future = null; } surface_view.onPause(); } /********** 再開 **********/ @Override protected void onResume() { super.onResume(); surface_view.onResume(); } /************ ビュー ************/ class BaseView extends GLSurfaceView { public BaseView(Context _context, BaseRenderer _renderer) { super(_context); setEGLContextClientVersion(2); // OpenGL ES 2.0使用 setEGLConfigChooser(new ConfigChooser()); setRenderer(_renderer); setRenderMode(RENDERMODE_WHEN_DIRTY); } class ConfigChooser implements GLSurfaceView.EGLConfigChooser { @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { final int[] configAttribs = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 8, EGL10.EGL_NONE }; int[] num_config = new int[1]; egl.eglChooseConfig(display, configAttribs, null, 0, num_config); EGLConfig[] configs = new EGLConfig[num_config[0]]; egl.eglChooseConfig(display, configAttribs, configs, num_config[0], num_config); return configs[0]; } } } /**************** レンダラー ****************/ class BaseRenderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10 _gl, EGLConfig _config) {} public void onSurfaceChanged(GL10 gl, int width, int height) { initNative(width, height); // native部初期化 if ( future == null ) { // 定期実行開始 future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { surface_view.requestRender(); // 描画リクエスト } }, 0, 1000/30, TimeUnit.MILLISECONDS); } } public void onDrawFrame(GL10 gl) { if ( !updateNative() ) { // native部稼働 finish(); } } } } /***************** End of File ***************************************************/
アクティビティのonCreateでサーフェスビューとレンダラーを作成するとか、System.loadLibraryでnative側のプログラムを読み込むとかいろいろありますが、この辺はNDKのプロラムでは共通事項なので説明は省きます。シンプルに作っているので、見れば大体わかると思います。
さて、プログラムの流れを追っていくと、
BaseActivityの onCreate → onResume
BaseViewの onResume
BaseRendererの onSurfaceCreated → onSurfaceChanged
と進みます。
ここでまず、nativeの関数 initNativeを呼んでnative側を初期化しておきます。
次に、「一定間隔でメインの処理」を行うための設定をします。
ScheduledExecutorServiceのメソッド
ScheduledFuture<?> scheduleAtFixedRate(
Runnable command,
long initialDelay,
long period,
TimeUnit unit)
時間単位 unitで、initialDelay後から delay毎にタスク commandを実行します。
このプログラムでは、
・1000/30ms毎に
・surface_view.requestRender()を実行する
となります。
BaseViewのコンストラクタで setRenderMode(RENDERMODE_WHEN_DIRTY)としているので、surface_viewの requestRender()により rendererの onDrawFrame()が呼ばれます。
よって、1000/ms毎に、nativeの関数 updateNativeが呼ばれることになります。
それでは、native側のプログラム
native.cpp
#include <jni.h> #include <android/log.h> static int cnt = 0; #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "INFO", __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "ERROR", __VA_ARGS__) extern "C" { JNIEXPORT void JNICALL Java_sys_BaseActivity_initNative(JNIEnv*, jobject, jint, jint); JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv*, jobject); } /************ 初期化 ************/ JNIEXPORT void JNICALL Java_sys_BaseActivity_initNative(JNIEnv*, jobject, jint _width, jint _height) { LOGI("initNative (%d, %d)", _width, _height); } /********** 稼働 **********/ JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv*, jobject) { if ( cnt % 60 == 0 ) { LOGI("updateNative %4d", cnt/60); } cnt++; return JNI_TRUE; } /**************** End of File *************************************************/
今回はログを表示するだけで、何もしません。
なお、初期化処理といいつつ initNativeは起動時だけではなくサスペンドからのレジューム時などにも呼ばれますので注意してください。
プロジェクト一式は、こちら。
OpenGLによる描画 ― 2013/11/03
正確にいうと、OpenGL ES 2.0になりますか。
よくあるサンプルですが、三角形ポリゴンを表示してみることにします。
と、その前に
・描画全体の初期化など、共通の処理
・アプリ毎の処理
を区別するために、今のうちにソースを分けておきます。
jni
├ sys
│ ├ common.h いろいろ共通事項のヘッダ
│ ├ SysMain.cpp native側のメイン処理
│ ├ Renderer.h 描画管理クラスヘッダ
│ ├ Renderer.cpp 描画管理クラスソース
├ def.h アプリの定義
└ AppMain.cpp アプリのメイン処理
SysMain.cpp
native側のメイン処理、java側から呼ばれる initNativeや updateNativeは、ここに移しました。
見ての通り初期化とフレーム毎の稼働で、描画管理とアプリの処理をそれぞれ呼んでいるだけです。
Renderer.h
Renderer.cpp
描画全体の管理をするクラス sys::Rendererです。
初期化処理は以下の通り。
・シェーダの初期化
OpenGL ES 2.0はプログラマブルシェーダなので、シェーダの設定が必要です。
今回は、
・頂点シェーダは、行列projectionによる座標変換
・フラグメントシェーダは、カラーcolorを設定
という、ごく単純なものになっています。
・座標変換行列の設定
座標はそのままだと左上(-1.0, -1.0)-右下(1.0, 1.0)になりますが、2Dゲームでこの座標ではどうにも使いにくいです。
画面の解像度を決めて、座標を変換して使うようにします。画面解像度はアプリ毎に決めるので、def.hに定義しておきます。
行列mat_projectionによる変換で、画面上の座標は、
(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)
になります。
2Dの画面で座標は、
(0, 0) - (SCREEN_WIDTH, -SCREEN_HEIGHT)
というのが多いとは思いますが、後に出てくるスプライト等も中心を原点にしたいということもあり、(0, 0)を中心とするような形にしました。
・ビューポートの設定
Androidの端末は画面解像度もバラバラなら、縦横の比率もバラバラです。
画面の比率をSCREEN_WIDTH、SCREEN_HEIGHTに合わせるようにglViewportでビューポートを設定しています。
次にフレーム毎の処理ですが、今回はとりあえずαブレンドだけしておきます。
アプリの処理
AppMain.cpp
初期化処理は、今回は単なる生ポリゴンなので何も無いです。
実際に三角形を描画する、フレーム毎の処理は以下の通りです。
・画面クリア
glClearで画面をクリアします。必ず必要な処理というわけではないので、sys::Rendererではなくアプリ側で行っています。デプスバッファでも使っていれば、共通処理でクリアしたほうが良いのでしょうが。
・シェーダプログラムの使用
今回のようにシェーダが一つしかないのであれば sys::Rendererで設定してしまっても良いのですが、後々シェーダの切り替えを行うことを考えてアプリ側で設定しています。
・三角形ポリゴンの描画
頂点座標・カラーを設定した後、glDrawArraysで GL_TRIANGLESを指定して三角形を描画します。
よくあるサンプルですが、三角形ポリゴンを表示してみることにします。
と、その前に
・描画全体の初期化など、共通の処理
・アプリ毎の処理
を区別するために、今のうちにソースを分けておきます。
jni
├ sys
│ ├ common.h いろいろ共通事項のヘッダ
│ ├ SysMain.cpp native側のメイン処理
│ ├ Renderer.h 描画管理クラスヘッダ
│ ├ Renderer.cpp 描画管理クラスソース
├ def.h アプリの定義
└ AppMain.cpp アプリのメイン処理
SysMain.cpp
#include "common.h" #include "Renderer.h" using namespace sys; extern "C" { JNIEXPORT void JNICALL Java_sys_BaseActivity_initNative(JNIEnv*, jobject, jint, jint); JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv*, jobject); } void init_app(void); // メイン初期化 Bool update_app(void); // メイン稼働 /************ 初期化 ************/ JNIEXPORT void JNICALL Java_sys_BaseActivity_initNative(JNIEnv* env, jobject, jint width, jint height) { LOGI("initNative (%d, %d)", width, height); Renderer::init(width, height); // 描画管理初期化 init_app(); // アプリメイン初期化 } /********** 稼働 **********/ JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv*, jobject) { Renderer::update(); // 描画管理稼働 if ( update_app() ) { // アプリメイン稼働 return JNI_TRUE; } return JNI_FALSE; // アプリ終了 } /**************** End of File *************************************************/
native側のメイン処理、java側から呼ばれる initNativeや updateNativeは、ここに移しました。
見ての通り初期化とフレーム毎の稼働で、描画管理とアプリの処理をそれぞれ呼んでいるだけです。
Renderer.h
#ifndef ___RENDERER_H___ #define ___RENDERER_H___ #include "common.h" #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> namespace sys { /************************ シェーダプログラム ************************/ struct ShaderProgram { GLuint program; // プログラムオブジェクト GLint position; // 座標 GLint color; // カラー GLuint projection; // 透視変換 ShaderProgram(void) // コンストラクタ { program = 0; } ~ShaderProgram() // デストラクタ { quit(); } void init(GLuint, GLuint); // 初期化 void use(const GLfloat*); // 使用 void quit(void); // 終了 }; /************** 描画管理 **************/ class Renderer { private : static void initShader(void); // シェーダ初期化 static GLuint loadShader(GLenum, const char*); // シェーダ作成 public : static ShaderProgram* shader; // シェーダプログラム static float mat_projection[4*4]; // 透視変換行列 static void init(int, int); // 初期化 static void quit(void); // 終了 static void update(void); // 稼働 static ShaderProgram* use_shader(void); // シェーダ使用 }; } #endif /********************* End of File ********************************/
Renderer.cpp
/*************************** 描画管理 ***************************/ #include "Renderer.h" namespace sys { ShaderProgram* Renderer::shader; // シェーダプログラム float Renderer::mat_projection[4*4]; // 透視変換行列 /************************************************ 初期化 引数 width, height = 端末画面サイズ ************************************************/ void Renderer::init(int width, int height) { initShader(); // シェーダ初期化 for (int i = 0; i < 4*4; i++) { // 透視変換行列初期化 mat_projection[i] = 0.0f; } mat_projection[0] = 2.0f/SCREEN_WIDTH; mat_projection[5] = -2.0f/SCREEN_HEIGHT; mat_projection[10] = 1.0f; mat_projection[15] = 1.0f; int _w, _h; if ( width*SCREEN_HEIGHT < height*SCREEN_WIDTH ) { // 横長(上下カット) _w = width; _h = width*SCREEN_HEIGHT/SCREEN_WIDTH; } else { // 縦長(左右カット) _w = height*SCREEN_WIDTH/SCREEN_HEIGHT; _h = height; } glViewport((width - _w)/2, (height - _h)/2, _w, _h); // ビューポート設定 } /******************** シェーダ初期化 ********************/ void Renderer::initShader(void) { shader = new ShaderProgram(); GLuint vert_shader, frag_shader; static const char gVertexShader[] = // 頂点シェーダプログラム "attribute vec4 position;" "attribute vec4 color;" "varying vec4 vColor;" "uniform mat4 projection;" "void main() {" "gl_Position = projection*position;" "vColor = color;" "}"; static const char gFragmentShader[] = // フラグメントシェーダプログラム "precision mediump float;" "varying vec4 vColor;" "void main() {" "gl_FragColor = vColor;" "}"; vert_shader = loadShader(GL_VERTEX_SHADER, gVertexShader); // 頂点シェーダ frag_shader = loadShader(GL_FRAGMENT_SHADER, gFragmentShader); // フラグメントシェーダ shader->init(vert_shader, frag_shader); // シェーダプログラム作成 glDeleteShader(vert_shader); glDeleteShader(frag_shader); } /******************************************* シェーダ作成 引数 type = シェーダ種類 source = プログラムソース 戻り値 シェーダオブジェクト *******************************************/ GLuint Renderer::loadShader(GLenum type, const char* source) { GLuint _shader = glCreateShader(type); // シェーダオブジェクト作成 GLint _compiled = 0; assert(_shader != 0); glShaderSource(_shader, 1, &source, NULL); // プログラムソース設定 glCompileShader(_shader); // コンパイル glGetShaderiv(_shader, GL_COMPILE_STATUS, &_compiled); // コンパイル結果取得 if ( !_compiled ) { // コンパイル失敗 GLchar* _buf; GLint _len; glGetShaderiv(_shader, GL_INFO_LOG_LENGTH, &_len); if ( (_len > 0) && (_buf = new GLchar[_len]) ) { glGetShaderInfoLog(_shader, _len, NULL, _buf); // エラーログ取得 LOGE("Could not compile shader %d:\n%s\n", type, _buf); delete[] _buf; } glDeleteShader(_shader); return 0; } return _shader; } /************************************************* シェーダプログラム作成 引数 v_shader = 頂点シェーダ f_shader = フラグメントシェーダ *************************************************/ void ShaderProgram::init(GLuint v_shader, GLuint f_shader) { program = glCreateProgram(); // プログラムオブジェクト作成 assert(program != 0); glAttachShader(program, v_shader); // 頂点シェーダアタッチ glAttachShader(program, f_shader); // フラグメントシェーダアタッチ GLint _linked = GL_FALSE; glLinkProgram(program); // リンク glGetProgramiv(program, GL_LINK_STATUS, &_linked); // リンク結果取得 if ( !_linked ) { // リンク失敗 GLchar* _buf; GLint _len; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &_len); if ( (_len > 0) && (_buf = new GLchar[_len]) ) { glGetProgramInfoLog(program, _len, NULL, _buf); // エラーログ取得 LOGE("Could not link program:\n%s\n", _buf); delete[] _buf; } glDeleteProgram(program); program = 0; return; } position = glGetAttribLocation(program, "position"); // 座標 color = glGetAttribLocation(program, "color"); // カラー projection = glGetUniformLocation(program, "projection"); // 透視変換 } /********** 終了 **********/ void Renderer::quit(void) { delete shader; // シェーダ削除 } void ShaderProgram::quit(void) { if ( program ) { glDeleteProgram(program); } } /********** 稼働 **********/ void Renderer::update(void) { glEnable(GL_BLEND); // αブレンド初期化 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } /************************** シェーダ使用 戻り値 シェーダ **************************/ ShaderProgram* Renderer::use_shader(void) { shader->use(mat_projection); return shader; } void ShaderProgram::use(const GLfloat* mat_projection) { glUseProgram(program); glUniformMatrix4fv(projection, 1, GL_FALSE, mat_projection); glEnableVertexAttribArray(position); glEnableVertexAttribArray(color); } } /**************** End of File ******************************************/
描画全体の管理をするクラス sys::Rendererです。
初期化処理は以下の通り。
・シェーダの初期化
OpenGL ES 2.0はプログラマブルシェーダなので、シェーダの設定が必要です。
今回は、
・頂点シェーダは、行列projectionによる座標変換
・フラグメントシェーダは、カラーcolorを設定
という、ごく単純なものになっています。
・座標変換行列の設定
座標はそのままだと左上(-1.0, -1.0)-右下(1.0, 1.0)になりますが、2Dゲームでこの座標ではどうにも使いにくいです。
画面の解像度を決めて、座標を変換して使うようにします。画面解像度はアプリ毎に決めるので、def.hに定義しておきます。
#ifndef ___DEF_H___ #define ___DEF_H___ static const int SCREEN_WIDTH = 640, // 画面サイズ SCREEN_HEIGHT = 960; #endif /********************** End of File ****************************************************/
行列mat_projectionによる変換で、画面上の座標は、
(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)
になります。
2Dの画面で座標は、
(0, 0) - (SCREEN_WIDTH, -SCREEN_HEIGHT)
というのが多いとは思いますが、後に出てくるスプライト等も中心を原点にしたいということもあり、(0, 0)を中心とするような形にしました。
・ビューポートの設定
Androidの端末は画面解像度もバラバラなら、縦横の比率もバラバラです。
画面の比率をSCREEN_WIDTH、SCREEN_HEIGHTに合わせるようにglViewportでビューポートを設定しています。
次にフレーム毎の処理ですが、今回はとりあえずαブレンドだけしておきます。
アプリの処理
AppMain.cpp
#include "common.h" #include "Renderer.h" /************ 初期化 ************/ void init_app(void) { } /****************************** 稼働 戻り値 アプリ続行か ******************************/ Bool update_app(void) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 画面クリア static const GLfloat vertex[] = { 0.0f, -480.0f, -320.0f, 480.0f, 320.0f, 480.0f }; static const GLubyte color[] = { 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, }; sys::ShaderProgram* _shader = sys::Renderer::use_shader(); // シェーダ使用 glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, color); // カラー glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, vertex); // 頂点 glDrawArrays(GL_TRIANGLES, 0, 3); // 三角形描画 return TRUE; } /**************** End of File *************************************************/
初期化処理は、今回は単なる生ポリゴンなので何も無いです。
実際に三角形を描画する、フレーム毎の処理は以下の通りです。
・画面クリア
glClearで画面をクリアします。必ず必要な処理というわけではないので、sys::Rendererではなくアプリ側で行っています。デプスバッファでも使っていれば、共通処理でクリアしたほうが良いのでしょうが。
・シェーダプログラムの使用
今回のようにシェーダが一つしかないのであれば sys::Rendererで設定してしまっても良いのですが、後々シェーダの切り替えを行うことを考えてアプリ側で設定しています。
・三角形ポリゴンの描画
頂点座標・カラーを設定した後、glDrawArraysで GL_TRIANGLESを指定して三角形を描画します。
食品偽装だらけ ― 2013/11/04
阪急阪神ホテルズのレストランでの食品偽装が発覚して以来、全国各地のホテルで次々に同じような偽装が明るみになっているようです。
これだけあると、最初に批判された阪急阪神ホテルズでは「どこもやってることだ」と言いたかったんじゃないでしょうか?
むしろ便乗ではなく、自分のところで問題を発覚させたという点では他のホテルよりマシなのではないかとも思ってしまいます。
ただ種類とか産地とか明らかな間違いはともかく、「フレッシュ」みたいな曖昧な表現は偽装の判断が難しいですけどね。
「一口ひれかつ」なんか一口で食べられたためしがないですし。
この前もラーメン屋で注文した「具だくさんつけ麺」が全然具だくさんに見えませんでしたが、これはどうも間違って普通のつけ麺を出していたらしい。
これだけあると、最初に批判された阪急阪神ホテルズでは「どこもやってることだ」と言いたかったんじゃないでしょうか?
むしろ便乗ではなく、自分のところで問題を発覚させたという点では他のホテルよりマシなのではないかとも思ってしまいます。
ただ種類とか産地とか明らかな間違いはともかく、「フレッシュ」みたいな曖昧な表現は偽装の判断が難しいですけどね。
「一口ひれかつ」なんか一口で食べられたためしがないですし。
この前もラーメン屋で注文した「具だくさんつけ麺」が全然具だくさんに見えませんでしたが、これはどうも間違って普通のつけ麺を出していたらしい。
assetsファイル読み込み ― 2013/11/04
三角ポリゴンの描画ができたので、次はテクスチャの描画に移ります。
テクスチャのデータは assetsフォルダに置いておきます。res/rawに置くという手もありますが、assetsの方がいろいろ融通がききそうなので。
ただ、assetsフォルダからの読み込みにもいくつか手順がありますので、今回はその辺りをやってみます。
assetsの読み込みには幾通りかのやり方があるようなのですが、Android OS 2.3以降で使える AssetManagerを使う方法にしてみます。
BaseActivity.java
AssetManagerは java側で取得できるので、initNativeで native側に送ります。
SysMain.cpp
それを native側で受け取って、AAssetManagerに変換します。
ファイル読み込み用の関数です。
データバッファを mallocで取っているので、使い終わったら freeで解放してください。
ファイル読み込みのサンプルです。
assetsフォルダには、test.txtというテキスト(終端に'\0'を追加)を置いておきます。
AppMain.cpp
load_assetで文字列データを読み込んで、ログに出力しています。
最後についでなんですが、アプリの定義は native側に持っていこうと、1フレームの間隔は initNativeの戻り値で native側から取得するように変更しました。
プロジェクト一式はこちらから。
テクスチャのデータは assetsフォルダに置いておきます。res/rawに置くという手もありますが、assetsの方がいろいろ融通がききそうなので。
ただ、assetsフォルダからの読み込みにもいくつか手順がありますので、今回はその辺りをやってみます。
assetsの読み込みには幾通りかのやり方があるようなのですが、Android OS 2.3以降で使える AssetManagerを使う方法にしてみます。
BaseActivity.java
public void onSurfaceChanged(GL10 gl, int width, int height) { int period = initNative(width, height, getAssets()); // native部初期化 if ( future == null ) { // 定期実行開始 future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { surface_view.requestRender(); // 描画リクエスト } }, 0, period, TimeUnit.MILLISECONDS); } }
AssetManagerは java側で取得できるので、initNativeで native側に送ります。
SysMain.cpp
asset_manager = AAssetManager_fromJava(env, mgr); // asset読み込みマネージャー assert(asset_manager != NULL);
それを native側で受け取って、AAssetManagerに変換します。
namespace sys { AAssetManager* asset_manager; // asset読み込み用 /**************************************** assetファイル読み込み 引数 name = ファイル名 戻り値 データ size:データサイズ ****************************************/ void* load_asset(const char* name, u32* size) { AAsset* _asset = AAssetManager_open(asset_manager, name, AASSET_MODE_BUFFER); assert(_asset); size_t _size = AAsset_getLength(_asset); // ファイルサイズ void* _buf = malloc(_size); // データバッファ AAsset_read(_asset, _buf, _size); // データ読み込み AAsset_close(_asset); if ( size ) { *size = (u32)_size; } return _buf; } }
ファイル読み込み用の関数です。
データバッファを mallocで取っているので、使い終わったら freeで解放してください。
ファイル読み込みのサンプルです。
assetsフォルダには、test.txtというテキスト(終端に'\0'を追加)を置いておきます。
AppMain.cpp
/************ 初期化 ************/ void init_app(void) { u32 _size; char* _str = (char*)sys::load_asset("test.txt", &_size); // テスト文字列 LOGI("[%s] size:%d", _str, (int)_size); free(_str); }
load_assetで文字列データを読み込んで、ログに出力しています。
最後についでなんですが、アプリの定義は native側に持っていこうと、1フレームの間隔は initNativeの戻り値で native側から取得するように変更しました。
プロジェクト一式はこちらから。
最近のコメント