サウンド - サスペンド・レジューム ― 2013/11/21
サンプルではBGMが流れ続けますが、一旦サスペンドで音を止めると次にレジュームしても止まったままです。
再開できるように、サウンドのサスペンド・レジューム処理を追加します。
Sound.cpp
再生(SL_PLAYSTATE_PLAYING)や停止(SL_PLAYSTATE_STOPPED)と同様に、SetPlayStateで一時停止(SL_PLAYSTATE_PAUSED)の状態を設定します。
再開は準備からの再生と同じ処理になります。
サンプル
AppMain.cpp
サスペンド・レジューム時に、それぞれプレイヤーのサスペンド・レジュームを呼んでいる…だけです。
プロジェクト一式は、こちらから。
再開できるように、サウンドのサスペンド・レジューム処理を追加します。
Sound.cpp
/**************
一時停止
**************/
void SoundPlayer::pause(void)
{
if ( state == PLAYING ) { // 再生中
if ( bqPlayerObject ) {
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); // 一時停止状態
}
state = PAUSED;
}
}
/**********
再開
**********/
void SoundPlayer::resume(void)
{
if ( state == PAUSED ) { // 一時停止中
play();
}
}
再生(SL_PLAYSTATE_PLAYING)や停止(SL_PLAYSTATE_STOPPED)と同様に、SetPlayStateで一時停止(SL_PLAYSTATE_PAUSED)の状態を設定します。
再開は準備からの再生と同じ処理になります。
サンプル
AppMain.cpp
/**************
一時停止
**************/
void pause_app(void)
{
for (int i = 0; i < 5; i++) {
sound_player[i].pause();
}
}
/**********
再開
**********/
void resume_app(void)
{
for (int i = 0; i < 5; i++) {
sound_player[i].resume();
}
}
サスペンド・レジューム時に、それぞれプレイヤーのサスペンド・レジュームを呼んでいる…だけです。
プロジェクト一式は、こちらから。
サウンド - 音量設定 ― 2013/11/20
前回サウンドの最低限の処理として音を鳴らせるようにしましたが、今回はサウンドの音量を変更できるようにしてみます。
主な追加・変更点です。
Sound.cpp
サウンド再生準備関数 prepare内の処理です。prepareの引数に音量 _volが追加されています。
OpenSLで音量を設定するためのインタフェース bqPlayerVolumeを取得しています。
音量設定関数です。
音量は 0.0(最小)~1.0(最大)で設定するようにしていますが、SetVolumeLevelはミリベルで指定するようになっていますので常用対数で変換しています。
全体の音量を変更できるように、マスター音量 master_volumeを静的メンバとして持っています。引数の無い方の set_volumeは、マスター音量を変更したときに既に鳴っているサウンドの音量を再設定するために呼び出します。
マスター音量変更のための静的メンバ関数 set_master_volumeです。
変更がなければ個々のサウンドの再設定は要らないので、変更の有無を戻り値として返すようにしています。
サンプルの変更部分です。
AppMain.cpp
画面をタッチする度に、BGMの音量を上げたり下げたりしています。
プロジェクト一式は、こちらから。
主な追加・変更点です。
Sound.cpp
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); assert(SL_RESULT_SUCCESS == result); // 音量インタフェース set_volume(_vol); // 音量設定
サウンド再生準備関数 prepare内の処理です。prepareの引数に音量 _volが追加されています。
OpenSLで音量を設定するためのインタフェース bqPlayerVolumeを取得しています。
/*****************************************************
音量設定
引数 _vol = 音量(0.0:最小 ~ 1.0:最大)
*****************************************************/
void SoundPlayer::set_volume(float _vol)
{
volume = _vol;
set_volume();
}
void SoundPlayer::set_volume(void)
{
if ( bqPlayerObject ) {
float _vol = volume*master_volume;
SLresult result;
result = (*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, (_vol >= 1.0f) ? 0 : ((_vol < 0.01f) ? -16000
: (SLmillibel)(8000.0f*log10f(_vol))));
assert(SL_RESULT_SUCCESS == result);
}
}
音量設定関数です。
音量は 0.0(最小)~1.0(最大)で設定するようにしていますが、SetVolumeLevelはミリベルで指定するようになっていますので常用対数で変換しています。
全体の音量を変更できるように、マスター音量 master_volumeを静的メンバとして持っています。引数の無い方の set_volumeは、マスター音量を変更したときに既に鳴っているサウンドの音量を再設定するために呼び出します。
/*************************************************************
マスター音量設定
引数 _vol = マスター音量(0.0:最小 ~ 1.0:最大)
戻り値 マスター音量が変更されたか
*************************************************************/
Bool SoundPlayer::set_master_volume(float _vol)
{
if ( master_volume != _vol ) {
master_volume = _vol;
return TRUE;
}
return FALSE;
}
マスター音量変更のための静的メンバ関数 set_master_volumeです。
変更がなければ個々のサウンドの再設定は要らないので、変更の有無を戻り値として返すようにしています。
サンプルの変更部分です。
AppMain.cpp
if ( sys::TouchPanel[0].flag & sys::TouchManager::TRIGGER ) {
sound_player[se_track].play(sound_data[SOUND_SE], sound_size[SOUND_SE]); // SE再生
se_track = ++se_track % 4;
sound_player[4].set_volume((se_track % 2) ? 0.5f : 1.0f); // BGM音量変更
}
画面をタッチする度に、BGMの音量を上げたり下げたりしています。
プロジェクト一式は、こちらから。
OpenSLでサウンド再生 ― 2013/11/19
OpenSLによるサウンドの再生です。
今回は最低限の処理として、PCMデータの再生を行います。
NDKにあるサンプル native-audioを参考にしていますが、データはWAVファイルから読み込むようにしています。
サウンドプレイヤークラス sys::SoundPlayer
Sound.h
静的メンバが、サウンドシステム全体として必要な共通部分です。
native-audioを見ると outputMixオブジェクトは Engineオブジェクトを作成するとき一緒に作成していますが、プレイヤー毎に作成しなければいけないようです。
Sound.cpp
サウンドシステム全体で必要なエンジン(Engine)オブジェクトとインタフェースを作成・初期化しています。
プレイヤーのコンストラクタ(及び、デストラクタ)です。
エンジンインタフェースから、出力(outputMix)オブジェクトを作成しています。
肝心のプレイヤーオブジェクトはPCMデータのフォーマットが必要なので、再生時に作成します。
サウンドの再生部分です。
再生されるまでそこそこ処理があるので、準備(prepare)と再生(play)の関数を分けています。
まず124~146行で WAVデータのヘッダから PCMのフォーマットを設定します。
そして sound_data・sound_sizeに PCMデータ本体のアドレスとサイズを格納しておきます。
次に156行でプレイヤーオブジェクトを作成して、175行までいろいろ OpenSLとして必要な設定を行います(今回音量の変更は行っていませんが、とりあえずインタフェースの取得だけ行っています)。
177行の Enqueueでサウンドデータをキューに送ります。
その後、183行で状態を SL_PLAYSTATE_PLAYINGに設定することによって再生が開始されます。
再生コールバックです。
ループ回数が残っていれば、再びデータをキューに送ります。
無ければ SL_PLAYSTATE_STOPPEDで停止状態にします。
停止処理です。
停止状態にしたうえで、プレイヤーオブジェクトを削除しておきます。
※再生コールバックの停止でも削除を試してみましたが、その後の処理が行われませんでした。Destroyした時点で、コールバック処理自体が切られてまうようです。
最後にサンプルプログラムです。
AppMain.cpp
サウンドプレイヤー:sound_player
4:BGM用
0~3:SE用
BGMは初期化時、SEは画面タッチしたときに再生します。
プロジェクト一式は、こちらから。
今回は最低限の処理として、PCMデータの再生を行います。
NDKにあるサンプル native-audioを参考にしていますが、データはWAVファイルから読み込むようにしています。
サウンドプレイヤークラス sys::SoundPlayer
Sound.h
#ifndef ___SOUND_H___
#define ___SOUND_H___
#include "common.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
namespace sys
{
/************************
サウンドプレイヤー
************************/
class SoundPlayer
{
public :
static SLObjectItf engineObject; // エンジンオブジェクト
static SLEngineItf engineEngine; // インタフェース
static void init_engine(void); // 初期化
static void quit_engine(void); // 終了
private :
SLObjectItf outputMixObject; // 出力オブジェクト
SLObjectItf bqPlayerObject; // プレイヤーオブジェクト
SLPlayItf bqPlayerPlay; // インタフェース
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; // バッファキューインタフェース
SLVolumeItf bqPlayerVolume; // 音量インタフェース
char* sound_data; // サウンドデータ
u32 sound_size; // サウンドデータサイズ
int sound_loop; // ループカウンタ
public :
SoundPlayer(void); // コンストラクタ
~SoundPlayer(); // デストラクタ
void play(const void*, u32, int _loop = 1); // 再生
void play(void);
void prepare(const void*, u32, int _loop = 1); // 再生準備
void stop(void); // 停止
void callback_wav(void); // 再生コールバック
};
}
#endif
/********************* End of File ********************************/
静的メンバが、サウンドシステム全体として必要な共通部分です。
native-audioを見ると outputMixオブジェクトは Engineオブジェクトを作成するとき一緒に作成していますが、プレイヤー毎に作成しなければいけないようです。
Sound.cpp
/********************
エンジン初期化
********************/
void SoundPlayer::init_engine(void)
{
SLresult result;
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); // エンジンオブジェクト作成
assert(SL_RESULT_SUCCESS == result);
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // リアライズ
assert(SL_RESULT_SUCCESS == result);
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
assert(SL_RESULT_SUCCESS == result); // インタフェース取得
}
/******************
エンジン終了
******************/
void SoundPlayer::quit_engine(void)
{
if ( engineObject ) {
(*engineObject)->Destroy(engineObject);
engineObject = NULL;
}
}
サウンドシステム全体で必要なエンジン(Engine)オブジェクトとインタフェースを作成・初期化しています。
/********************
コンストラクタ
********************/
SoundPlayer::SoundPlayer(void)
{
SLresult result;
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result); // 出力オブジェクト作成
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result); // リアライズ
bqPlayerObject = NULL;
}
/******************
デストラクタ
******************/
SoundPlayer::~SoundPlayer()
{
stop();
(*outputMixObject)->Destroy(outputMixObject);
}
プレイヤーのコンストラクタ(及び、デストラクタ)です。
エンジンインタフェースから、出力(outputMix)オブジェクトを作成しています。
肝心のプレイヤーオブジェクトはPCMデータのフォーマットが必要なので、再生時に作成します。
struct WaveFormat
{
s8 riff[4];
u32 total_size;
s8 fmt[8];
u32 fmt_size;
u16 format;
u16 channel;
u32 rate;
u32 avgbyte;
u16 block;
u16 bit;
s8 data[4];
u32 data_size;
};
/****************************************************
再生
引数 _data = サウンドデータ
_size = サウンドデータサイズ
_loop = ループ回数(0:無限ループ)
****************************************************/
void SoundPlayer::prepare(const void* _data, u32 _size, int _loop)
{
stop();
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataFormat_PCM format_pcm;
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
switch ( *((u32*)_data) ) { // データフォーマット
case 0x46464952 : // WAV
{
WaveFormat* _info = (WaveFormat*)_data;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = (SLuint32)_info->channel;
format_pcm.samplesPerSec = (SLuint32)_info->rate*1000;
format_pcm.bitsPerSample = (SLuint32)_info->bit;
format_pcm.containerSize = (SLuint32)_info->bit;
format_pcm.channelMask = (_info->channel == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
sound_data = (char*)((u32)_data + sizeof(WaveFormat));
sound_size = _info->data_size;
sound_loop = _loop;
}
break;
default :
assert(FALSE);
break;
}
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
const SLInterfaceID ids[3] = {SL_IID_PLAY, SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result;
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
if ( SL_RESULT_SUCCESS != result ) { // プレイヤーオブジェクト作成
bqPlayerObject = NULL;
return;
}
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); // リアライズ
assert(SL_RESULT_SUCCESS == result);
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
assert(SL_RESULT_SUCCESS == result); // インタフェース取得
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
assert(SL_RESULT_SUCCESS == result); // バッファキューインタフェース
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
assert(SL_RESULT_SUCCESS == result); // 音量インタフェース
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallbackWAV, this);
assert(SL_RESULT_SUCCESS == result); // 再生コールバック設定
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data, (SLuint32)sound_size);
}
void SoundPlayer::play(void)
{
if ( bqPlayerObject ) {
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); // 再生開始
}
}
void SoundPlayer::play(const void* _data, u32 _size, int _loop)
{
prepare(_data, _size, _loop);
play();
}
サウンドの再生部分です。
再生されるまでそこそこ処理があるので、準備(prepare)と再生(play)の関数を分けています。
まず124~146行で WAVデータのヘッダから PCMのフォーマットを設定します。
そして sound_data・sound_sizeに PCMデータ本体のアドレスとサイズを格納しておきます。
次に156行でプレイヤーオブジェクトを作成して、175行までいろいろ OpenSLとして必要な設定を行います(今回音量の変更は行っていませんが、とりあえずインタフェースの取得だけ行っています)。
177行の Enqueueでサウンドデータをキューに送ります。
その後、183行で状態を SL_PLAYSTATE_PLAYINGに設定することによって再生が開始されます。
/**********************
再生コールバック
**********************/
static
void bqPlayerCallbackWAV(SLAndroidSimpleBufferQueueItf, void* context)
{
((SoundPlayer*)context)->callback_wav();
}
void SoundPlayer::callback_wav(void)
{
if ( sound_loop == 1 ) {
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); // 停止
return;
}
if ( sound_loop > 1 ) {
sound_loop--;
}
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, sound_data, (SLuint32)sound_size); // 再生開始
}
再生コールバックです。
ループ回数が残っていれば、再びデータをキューに送ります。
無ければ SL_PLAYSTATE_STOPPEDで停止状態にします。
/**********
停止
**********/
void SoundPlayer::stop(void)
{
if ( bqPlayerObject ) { // 再生中
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); // 停止状態
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = NULL;
}
}
停止処理です。
停止状態にしたうえで、プレイヤーオブジェクトを削除しておきます。
※再生コールバックの停止でも削除を試してみましたが、その後の処理が行われませんでした。Destroyした時点で、コールバック処理自体が切られてまうようです。
最後にサンプルプログラムです。
AppMain.cpp
#include "common.h"
#include "Sprite.h"
#include "TouchPanel.h"
#include "Sound.h"
// スプライト
enum
{
SPR_PHOTO = 0, // 背景
SPR_MAX,
};
static sys::Sprite* sprite; // スプライト
// サウンドデータ
enum
{
SOUND_BGM = 0, // BGM
SOUND_SE, // SE
SOUND_MAX,
};
static sys::SoundPlayer* sound_player; // サウンドプレイヤー
static char* sound_data[SOUND_MAX]; // サウンドデータ
static u32 sound_size[SOUND_MAX]; // サウンドデータサイズ
static int se_track; // SEトラック番号
/************
初期化
************/
void init_app(void)
{
struct SprDef
{
const char* tex_name; // テクスチャファイル名
SRect coord; // UV座標
};
static const
SprDef spr_def[SPR_MAX] =
{
{"photo.pkm", { 0, 0, 640, 960}}, // 背景
};
sprite = new sys::Sprite[SPR_MAX]; // スプライト初期化
for (int i = 0; i < SPR_MAX; i++) {
sprite[i].set(sys::TexCache::RES_ASSET, spr_def[i].tex_name, &spr_def[i].coord);
}
sound_player = new sys::SoundPlayer[5]; // サウンドプレイヤー
sound_data[SOUND_BGM] = (char*)sys::load_asset("bgm.wav", &sound_size[SOUND_BGM]); // サウンドデータ読み込み
sound_data[SOUND_SE] = (char*)sys::load_asset("se.wav", &sound_size[SOUND_SE]);
se_track = 0;
sound_player[4].play(sound_data[SOUND_BGM], sound_size[SOUND_BGM], 0); // BGM再生開始
}
/**********
終了
**********/
void quit_app(void)
{
delete[] sound_player;
for (int i = 0; i < SOUND_MAX; i++) {
free(sound_data[i]);
}
delete[] sprite;
}
/******************************
稼働
戻り値 アプリ続行か
******************************/
Bool update_app(void)
{
sprite[SPR_PHOTO].draw(0.0f, 0.0f); // 背景
if ( sys::TouchPanel[0].flag & sys::TouchManager::TRIGGER ) {
sound_player[se_track].play(sound_data[SOUND_SE], sound_size[SOUND_SE]); // SE再生
se_track = ++se_track % 4;
}
return TRUE;
}
/**************
一時停止
**************/
void pause_app(void)
{
for (int i = 0; i < 5; i++) {
sound_player[i].stop();
}
}
/**********
再開
**********/
void resume_app(void)
{
}
/**************** End of File *************************************************/
サウンドプレイヤー:sound_player
4:BGM用
0~3:SE用
BGMは初期化時、SEは画面タッチしたときに再生します。
プロジェクト一式は、こちらから。
Android NDK サウンド再生指針 ― 2013/11/18
さて、Android NDKプログラミングもそろそろサウンドの再生に取り掛かりたいと思います。
サウンドデータの管理ですが、
・音を鳴らす度にファイルから読み込むのでは重い
・PCMデータをメモリに置いておくのは容量を食う
ということで、
「メモリ上の圧縮フォーマットデータをデコードしながら再生」
ができるようにしたいです(アプリの規模や状態で常にそれがベストとは限りませんが、少なくとも対応しておきたい)。
しかし、Androidのサウンドは
・SDKの SoundPoolは圧縮フォーマットをリソースにできるけど、デコードしてメモリに常駐
・SDKの MediaPlayerはファイル指定のみ
・NDKの OpenSLは、圧縮ファイルの再生はファイル指定のみ
と、なかなか一筋縄ではいかないようです。
仕方が無いので、圧縮データ(問題なさそうなOgg Vorbisフォーマット)をデコードしつつ、OpenSLのPCM再生を使用するという方法で何とか進めてみたいと思います。
ああ、面倒くさい。
サウンドデータの管理ですが、
・音を鳴らす度にファイルから読み込むのでは重い
・PCMデータをメモリに置いておくのは容量を食う
ということで、
「メモリ上の圧縮フォーマットデータをデコードしながら再生」
ができるようにしたいです(アプリの規模や状態で常にそれがベストとは限りませんが、少なくとも対応しておきたい)。
しかし、Androidのサウンドは
・SDKの SoundPoolは圧縮フォーマットをリソースにできるけど、デコードしてメモリに常駐
・SDKの MediaPlayerはファイル指定のみ
・NDKの OpenSLは、圧縮ファイルの再生はファイル指定のみ
と、なかなか一筋縄ではいかないようです。
仕方が無いので、圧縮データ(問題なさそうなOgg Vorbisフォーマット)をデコードしつつ、OpenSLのPCM再生を使用するという方法で何とか進めてみたいと思います。
ああ、面倒くさい。
タッチパネル入力 その2 ― 2013/11/17
前回に引き続きタッチパネルからの入力、今回は native側の処理を行います。
タッチパネル入力管理クラス sys::TouchManagerです。
TouchPanel.h
TouchPanel.cpp
マルチタッチ対応のため、配列 TouchPanelで複数のインスタンスを処理します。
フレーム毎の処理 updateでは javaで取得した状態を受け取り、各メンバに格納します。
タッチしているときは、flagのビットTOUCHを立てます。
タッチ位置座標は画面描画と同じ、(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)になるように変換します。
基本的にはこれだけですが、他によく使う状態を計算しておきます。
flagのビット
・TRIG 離した状態からタッチしたとき
・RELEASE タッチした状態から離したとき
・REPEAT TRIGと同じですが、押しっぱなしのとき一定間隔でビットが立ちます
move_x, move_y
タッチしているときの前フレームからの移動距離
次に、TouchManagerを呼び出すシステム部分です。
SysMain.cpp
初期化・終了部分は、それぞれ init_managerと quit_managerを呼んでいるだけなので省略しています。
稼働部分では javaから受け取った jbyteArrayから GetPrimitiveArrayCriticalで配列のポインタを取得して TouchManagerに渡しています。
※javaから配列を渡すときはいくつか方法があり、GetPrimitiveArrayCriticalはそのままポインタを渡す(らしい)ので高速なのですが、その分いろいろと制限がありますので使うときは気をつけてください。
サンプルのタッチパネルテスト部分です。
タッチしたところにビー玉の絵が表示されます。
AppMain.cpp
タッチパネル入力管理クラス sys::TouchManagerです。
TouchPanel.h
/********************************
タッチパネル
********************************/
#ifndef ___TOUCH_PANEL_H___
#define ___TOUCH_PANEL_H___
#include "common.h"
namespace sys
{
/**********************
タッチパネル管理
**********************/
class TouchManager
{
public :
static void init_manager(void); // 管理システム初期化
static void quit_manager(void); // 管理システム終了
static void update_manager(short const*); // 管理システム稼働
private :
int repeat_cnt; // リピート用カウンタ
int prev_x, prev_y; // 前回のタッチ位置
public :
static const int TOUCH_MAX = 5; // マルチタッチ数
enum
{
TOUCH = (1 << 0), // タッチ
TRIG = (1 << 1), // 初回タッチ
REPEAT = (1 << 2), // リピート
RELEASE = (1 << 3), // リリース
};
u32 flag; // タッチ状態フラグ
int x, y; // タッチ位置
int move_x, move_y; // 移動距離
TouchManager(void) // コンストラクタ
{
flag = 0x00;
}
void update(short const*); // 稼働
};
extern TouchManager* TouchPanel;
}
#endif
/*************** End of File ****************************************/
TouchPanel.cpp
/***********************************
タッチパネル管理
***********************************/
#include "TouchPanel.h"
#include "Renderer.h"
namespace sys
{
TouchManager* TouchPanel;
/************
初期化
************/
void TouchManager::init_manager(void)
{
TouchPanel = new TouchManager[TOUCH_MAX];
}
/**********
終了
**********/
void TouchManager::quit_manager(void)
{
delete[] TouchPanel;
}
/***********************************************
稼働
引数 _status[0] = タッチしているか
_status[1] = X座標
_status[2] = Y座標
***********************************************/
void TouchManager::update_manager(short const* _status)
{
for (int i = 0; i < TOUCH_MAX; i++) {
TouchPanel[i].update(_status);
_status += 3;
}
}
void TouchManager::update(short const* _status)
{
if ( _status[0] ) { // タッチ中
// タッチ位置補正
x = (_status[1] - sys::Renderer::screen_rect.x)*SCREEN_WIDTH/sys::Renderer::screen_rect.w - SCREEN_WIDTH/2;
y = (_status[2] - sys::Renderer::screen_rect.y)*SCREEN_HEIGHT/sys::Renderer::screen_rect.h - SCREEN_HEIGHT/2;
if ( !(flag & TOUCH) ) { // 初回
flag = TOUCH | TRIG | REPEAT;
repeat_cnt = TOUCH_REPEAT1;
move_x = 0;
move_y = 0;
prev_x = x;
prev_y = y;
}
else { // 継続
flag = TOUCH;
if ( --repeat_cnt == 0 ) { // リピート
flag |= REPEAT;
repeat_cnt = TOUCH_REPEAT2;
}
move_x = x - prev_x;
move_y = y - prev_y;
prev_x = x;
prev_y = y;
}
}
else { // 非タッチ
flag = (flag & TOUCH) ? RELEASE : 0;
}
}
}
/*************** End of File *****************************************/
マルチタッチ対応のため、配列 TouchPanelで複数のインスタンスを処理します。
フレーム毎の処理 updateでは javaで取得した状態を受け取り、各メンバに格納します。
タッチしているときは、flagのビットTOUCHを立てます。
タッチ位置座標は画面描画と同じ、(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)になるように変換します。
基本的にはこれだけですが、他によく使う状態を計算しておきます。
flagのビット
・TRIG 離した状態からタッチしたとき
・RELEASE タッチした状態から離したとき
・REPEAT TRIGと同じですが、押しっぱなしのとき一定間隔でビットが立ちます
move_x, move_y
タッチしているときの前フレームからの移動距離
次に、TouchManagerを呼び出すシステム部分です。
SysMain.cpp
/**********
稼働
**********/
JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv* env, jobject, jbyteArray touch)
{
Renderer::update(); // 描画管理稼働
jbyte* dst = (jbyte*)env->GetPrimitiveArrayCritical(touch, NULL);
TouchManager::update_manager((short*)dst); // タッチパネル管理稼働
env->ReleasePrimitiveArrayCritical(touch, dst, 0);
if ( update_app() ) { // アプリメイン稼働
Renderer::draw(); // 描画後処理
return JNI_TRUE;
}
init_flag = FALSE;
return JNI_FALSE; // アプリ終了
}
初期化・終了部分は、それぞれ init_managerと quit_managerを呼んでいるだけなので省略しています。
稼働部分では javaから受け取った jbyteArrayから GetPrimitiveArrayCriticalで配列のポインタを取得して TouchManagerに渡しています。
※javaから配列を渡すときはいくつか方法があり、GetPrimitiveArrayCriticalはそのままポインタを渡す(らしい)ので高速なのですが、その分いろいろと制限がありますので使うときは気をつけてください。
サンプルのタッチパネルテスト部分です。
タッチしたところにビー玉の絵が表示されます。
AppMain.cpp
for (int i = 0; i < sys::TouchManager::TOUCH_MAX; i++) { // タッチパネルテスト
if ( sys::TouchPanel[i].flag & sys::TouchManager::TOUCH ) {
sprite[SPR_BALL_BLUE + (i % 4)].draw(sys::TouchPanel[i].x, sys::TouchPanel[i].y, 3.0f + sinf((cnt % 60)*M_PI*2/60)*2.0f);
}
}
cnt++;

最近のコメント