AqualeadTips


目次

1. Aqualead全般Tips
1. SetUpdateFunc()に設定した関数へ値を渡す
2. SetUpdateFunc()で設定した関数から値を受け取る
3. 直前のタスクへ戻る簡単な方法
4. 乱数の結果を再現したい
5. ALRandInt()関数とGenInt()関数の違い
6. 画面サイズを取得したい
7. Try付き関数とは
8. 画面遷移をサポートするFSMとは
9. 何かボタンが押されるまで待機する処理を簡潔に書くには
2. デバッグ関連Tips
1. リリースビルド時にDebug出力を有効化する
2. 意図しない処理をエラーで止めたい
3. 設定値やデバッグ用の数値を外部ファイルで読み書きしたい
4. コリジョンの衝突領域を可視化したい
5. ALWidgetの状態変化などのログを確認したい
3. ALTable関連Tips
1. ALTableの内容が正しいか確認したい
2. 固有IDでテーブルのレコードを移動させる
3. 他の処理に影響を与えずにソートをする
4. ALTableをClone()する際の注意点
5. ALTable::IsEof()がtrueになるタイミングについて
4. サウンド関連Tips
1. ALSoundとALVoiceについて
2. ALSoundPlayer()について
3. 簡単にサウンドを再生したい
5. ALNode関連Tips
1. GetPos()とGetWorldPos()の違い
2. GetWorldPos()の注意点
3. グループと親子とは
4. Assemble()とAssembleMember()の違い
5. 指定色になったか判定したい
6. FindNode()の検索範囲について
7. 識別IDを設定する
8. 表示されているか判定する
9. フェードの上に何か表示するには
10. サイズを好きに変える事が出来るウィンドウ
11. HTML5でGetWorldPos()を使用する際の注意事項
12. ALIPMoveControllerの注意点
13. グループ関係の利点
6. スプライト関連Tips
1. ALImageとALTextureについて
2. パターンIDを利用したアニメーション
3. aedファイルを利用したアニメーション
7. ALWidget関連
1. ウィジェットのStateについて
2. ウィジェットのFocusとは
3. ウィジェットのFocusの初期状態
4. ウィジェットのdefaultOwner
5. スキンIDで動作のテンプレートを設定する
6. スクロールバー用のスキンを設定する
7. ウィジェットにOpenMotionが設定されている場合の注意点
8. 衝突判定の優先度と描画優先度
9. ウィジェットの有効/無効と表示/非表示
10. ウィジェットのClone()
11. スクロールバーのノブの位置を変更する
12. FindNode()でノードが見つからない
13. CellListWidgetの範囲外に出たCellについて
14. GrobalLockの注意点
15. 複数Sceneを使ったダイアログ

第1章 Aqualead全般Tips

1. SetUpdateFunc()に設定した関数へ値を渡す

あるALNodeに自動で処理をさせる場合に

  void ALUpdater::SetUpdateFunc( ALUPDATER_EVENT_FUNC func )

という関数に更新処理を登録することで、自動的に関数「func」の中の処理を実行させることが出来ます。

この時に関数「func」に値を渡したいと思っても、引き数を指定できない為、渡すことができません。

このような場合に

  void ALPropSet::SetUserData( void *userdata )

という関数を利用することで、関数「func」へ値を渡すことが出来ます。

(「ALNode」クラスは「ALUpdater」クラスを継承していて、「ALUpdater」クラスは「ALPropSet」クラスをj継承しています。)

※UserDataはメンバ変数として保持されているので、一度設定した値は意図的に書き換えるまで変化しません。

 意図的に初期化を行わない場合、どのような数値が設定されているか不明なので、極力初期化をするようにしてください。

 また使用し終わったら、初期化をすることをオススメします。

 値を変更せずに、意図せず処理が進むことを防ぐことで、バグを減らす事ができます。

簡単なサンプルコード

  // ある画像を毎フレーム、渡した値分拡大する更新処理
  void* TestFunc( ALNode* self ){
      float addScale = (float)self->GetUserData();    // 使用する値に合った型へキャストが必要です
      self->SetScale( self->GetScale() + addScale );
  }
  void Main(){
      ALSprite* pSprite = ALSprite::Create( TEST_TEXTURE_RESORCE_ID );    // ある画像を生成
      pSprite->SetUpdateFunc( TestFunc );     // 更新処理を登録
      pSprite->SetUserData( (void*)0.001f );    // 値を渡す。void*型で渡すこと
      while( true )    ALYield();
  }

2. SetUpdateFunc()で設定した関数から値を受け取る

あるALNodeに自動で処理をさせる場合に

  void ALUpdater::SetUpdateFunc( ALUPDATER_EVENT_FUNC func )

という関数に更新処理を登録することで、自動的に関数「func」の中の処理を実行させることが出来ます。 この時に関数「func」から計算結果や判定結果などを取得したいと思っても、返り値を取得できない為受け取れません。 このような場合に

  void ALPropSet::GetUserData( void *userdata )

という関数を利用することで、関数「func」から値を受け取る事が出来ます。 (「ALNode」クラスは「ALUpdater」クラスを継承していて、「ALUpdater」クラスは「ALPropSet」クラスをj継承しています。) ※UserDataはメンバ変数として保持されているので、一度設定した値は意図的に書き換えるまで変化しません。  意図的に初期化を行わない場合、どのような数値が設定されているか不明なので、極力初期化をするようにしてください。  また使用し終わったら、初期化をすることをオススメします。  値を変更せずに、意図せず処理が進むことを防ぐことで、バグを減らす事ができます。

簡単なサンプルコード

  // ある画像を毎フレーム拡大し、10.0fを超えたらbool値を返す更新処理
  void* TestFunc( ALNode* self ){
      self->SetScale( self->GetScale() + 0.00001f );
      if( self->getScale() > 10.0f )  self->SetUserData( (void*)true );    // 値を設定する。void*型へキャストが必要です
      else self->SetUserData( (void*)false );
  }
  void Main(){
      ALSprite* pSprite = ALSprite::Create( TEST_TEXTURE_RESORCE_ID );    // ある画像を生成
      pSprite->SetUpdateFunc( TestFunc );     // 更新処理を登録
      while( true ){
          if( (bool)pSprite->GetUserData() )    break;     // 値を受け取る。必要な型へキャストを行うこと
          ALYield();
      }
  }

3. 直前のタスクへ戻る簡単な方法

処理を大きな単位で分ける時に、「ALTask」クラスでタスクに分けて実装をしている場合、直前のタスクへ戻る必要がある場合があります。 例えば、戻るボタンを押された場合やバトルやイベントシーンの終了時などです。 この時に今いるタスクへ遷移するルートが複数あり、直前にいたタスクへ戻らなくてはならない場合、直前のタスクを覚えておいて判定をして戻り先を判定しなければなりません。 そんな時に、

  // あるタスクの更新処理
  ALTaskResult TestTask::DoExec( size_t param ){
      ・・・// タスクの更新処理
      // 戻り先は直前にいたタスク
      return ALTASK_ID_RETUREN;
  }

このように記述することで、自動的に直前にいたタスクへ遷移することできます。

4. 乱数の結果を再現したい

Aqualeadでは乱数を使用する場合に「ALRandom」クラスを使用します。 「ALRandom」クラスは擬似乱数なので、「シード値」を指定することで乱数の結果を再現することが出来ます。 「シード値」を設定するには

  // 生成時に引き数としてシード値を渡す
  ALRandom::Create( Uint32 seed )
  // シード値を設定する関数
  void ALRandom::SetSeed(Uint32 seed)

などの方法があります。

5. ALRandInt()関数とGenInt()関数の違い

整数の乱数値を返す関数として

  AL_INLINE MUint32 ALRandInt(){
       return ALRandom::GetDefault()->GenInt();
  }
  AL_INLINE MUint32 ALRandom::GenInt(){
      return static_cast< ALRandomImp * >( _Imp )->GenInt();
  }

という二つの関数があります。 関数の定義を見れば分かると思いますが、「ALRandInt()」関数はグローバルなスコープの関数です。ALRandom.hがIncludeされていれば、呼び出すことが出来ます。 一方「GenInt()」関数は、「ALRandom」クラスのメンバ関数です。「ALRandom」クラスのインスタンスが無ければ呼び出せません。 ※注意事項※ 自身でALRandomを生成しシード値を設定している場合は、この二つの関数を混ぜて使用しないでください。 関数定義を中身を見れば分かると思いますが、「ALRandInt()」関数は「GetDefault()」のインスタンスから乱数を生成しています。 「GetDefault()」で取得したインスタンスのシード値が、自身で設定したシード値と同じである確証はありません。

6. 画面サイズを取得したい

画面の中央や画面外の判定などをする為に、画面のサイズが知りたい場合があると思います。 その場合は

  ALScreen::GetDefault()->GetWidth();     // 横サイズ
  ALScreen::GetDefault()->GetHeight();    // 縦サイズ

でそれぞれ取得することができます。 「ALScreen::GetDefault()」関数で取得するインスタンスは、Aqualeadが自動的に生成するものなので、意図的に生成する必要はありません。

7. Try付き関数とは

Aqualeadの中には、通常の関数と「Try」が付いた関数が用意されているものもあります。 この二つの関数はそれぞれ、問題が発生したときの挙動が変わります。

  // RESOURCE_ID_Aが存在しない場合
  // Assertが発生し、処理が止まります。
  ALArchive* aar = ALArchive::Create( RESOURCE_ID_A );
  // NULLが返ってきますが、処理は続行されます。
  ALArchive* aar = ALArchive::CreateTry( RESOURCE_ID_A );
  if( aar == NULL )    break;  // NULLが返るので判定してスキップさせたりできます。

意図しないデータやエラーがでてゲームが止まるのを防ぐことが出来ますが、極力Assertでゲームを止めるように実装を進めてください。 その方が開発中にバグを見つけやすく、結果としてリリース後のバグを減らす事へ繋がります。

8. 画面遷移をサポートするFSMとは

画面遷移を実装する場合に、Aqualeadには便利なサポートクラス「AEMenuFSM」があります。 このクラスを利用することで、画面遷移の処理を0から作る必要が無くなります。 まず「FSM」について簡単に説明します。 「FSM」は「finite state machine」(有限状態機械)の略称です。 「有限状態機械」とはある「状態」を渡すことで、ある「処理」を実行する仕組みです。 詳しく調べると、難しいことが色々と出てくるので、簡単に説明しました。 「AEMenuFSM」は、「[状態]+[処理]」という情報を最初に定義し、ゲーム中に[状態]を切り替えて画面遷移を行います。

  enum SCENE_ID{     // 状態を指すID番号
      SCENE_ID_START,
      SCENE_ID_MAIN,
      SCENE_ID_BATTLE,
      SCENE_ID_END
  };
  // 各画面ごとの処理関数
  void StartExec( AEMenuState* state ){
      _Fsm->ChangeState( SCENE_ID_MAIN );    // メイン画面へ遷移
  }
  void MainExec( AEMenuState* state );
  void BattleExec( AEMenuState* state );
  void EndExec( AEMenuState* state );
  // グローバルなFsmインスタンス
  AEMenuFsm* _fsm = AEMenuFsm::Create();
  // [状態]+[処理]をFSMに登録
  Init(){
      // 状態を示すID番号、画面のメニューデータリソースID、画面処理関数
      _fsm->AddState( SCENE_ID_START, RESOURCE_START_SCENE, StartExec );
      _fsm->AddState( SCENE_ID_MAIN, RESOURCE_MAIN_SCENE, MainExec ;
      _fsm->AddState( SCENE_ID_BATTLE, RESOURCE_BATTLE_SCENE, BattleExec ;
      _fsm->AddState( SCENE_ID_END, RESOURCE_END_SCENE, EndExec ;
  }
  // Main関数
  void Main(){
      Init();
      _Fsm->ChangeState( SCENE_ID_START );
      while( true )    ALYield();
  }

簡単なサンプルソースです。 重要なのは、「AddState()」関数で登録を行い、「ChangeState()」で「状態」を切り替えると、次の更新タイミングでは、現在の「状態」に設定された「処理」が実行されるという点です。

9. 何かボタンが押されるまで待機する処理を簡潔に書くには

「AEMenuFsm」クラスに「AddState()」関数で状態と処理を登録した時に、「AEMenuState」クラスが渡され、登録した処理の中から、引き数を使って、アクセスすることができます。 この「AEMenuState」クラスを使う事で、ボタンを押して決定状態になるまで待機する処理を簡潔に記述することが出来ます。

  // 手動で決定ボタンを判定する場合
  void UpdateFunc( AEMenuState* state ){
      ALWidget* button = static_cast<ALButtonWidget>(state->GetMainWidget()->FindNode(ButtonID));
      while( true ){
          if( button->GetDecisionWidget() != NULL ){
              break;    // 決定状態のボタンがあったらループを抜ける
          }
          ALYield();
      }
      if( button->GetDecisionWidget()->GetTag() == GOTO_BATTLE_TAG_ID ){
          _Fsm->ChangeState( SCENE_ID_BATTLE );    // バトル処理へ遷移
      }
  }

  // AEMenuStateを利用する場合
  void UpdateFunc( AEMenuState* state ){
      ALWidget* button = state->ProcessDecisionWait();    // 何かボタンが押されるまで待機する
      // AEMenuFsmへきちんと登録されているなら、画面遷移に関するボタンについては、処理をかかなくても自動的に遷移する
  }

「ProcessDecisionWait()」関数を使うと短く記述できることが分かったと思います。 しかし問題もあります。 「ProcessDecisionWait()」関数を使うと、「ProcessDecisionWait()」関数が記述されている行で処理が止まってしまい、画面の更新処理などをループの中に組み込めなくなってしまいます。 そのような場合は、「ProcessDecision()」関数を使い、自分で判定文を追加することで対応できます。

  // ProcessDecision()を利用し毎回画面の要素を更新する
  void UpdateFunc( AEMenuState* state ){
      ALWidget* button = state->ProcessDecision();    // 決定状態のボタンのインスタンスを取得。無い場合NULLが返ってくる
      if( button == NULL ){    // 決定状態のWidgetがまだ無いなら
          MenuTextureUpdate();    // 画面の表示を更新する処理
      }
  }

第2章 デバッグ関連Tips

1. リリースビルド時にDebug出力を有効化する

リリースビルドを実行している時のみ発生するバグのチェック中などに、ログ出力がしたい場合などがあると思います。 その場合は、

  #define AL_USE_PRINTF 0

の定義を、「0」から「1」に修正してください。 デバッグのログ出力が、リリースビルドに有効になります。 リリース時有効になる「AL_USE_PRINTF」定義とデバッグ時有効になる「AL_USE_PRINTF」定義の2箇所あるので、変更箇所のミスには気をつけてください。 また、リリースビルド時にログを出力する設定のままファイルをコミットしないように注意してください。

2. 意図しない処理をエラーで止めたい

意図していない値や来るはずの無い値が渡された時、エラーを発生させそれ以上処理を進ませたくない場合があると思います。 このような場合

  ALAssert( 条件式, エラー文章文字列 );

を利用することで、その場でプログラムの処理を停止させることが出来ます。 ALAssertは「「条件式」が「false」になった場合に、処理を停止させます。

意図していない値や有り得ない値を処理する場合は、無視して次の処理へ進ませずに、極力アサートなどで処理を停止するようにした方が、バグの発見や調査が楽になります。 できるだけアサートで停止させることをオススメします。

またエラー文章の内容は、分かりやすく書くことを心がけましょう。 アサートが発生してエラー文章が出ても、意味が分からなければ、意味がありません。

3. 設定値やデバッグ用の数値を外部ファイルで読み書きしたい

ゲームにおける細かい動作の調整や、ユーザーが書き換えても問題無いような設定値などを、ソースコードのコンパイルや、セーブデータとしてバイナリ形式で保存したりせずに、読み書きできる方法を紹介します。

  ALStringList *sl = ALStringList::Create();
  if( sl->LoadTry("GameDef.txt") ){
      Sint32 param = ALStrToInt( sl->FindGetValue("VoiceVolume") );
      if( param != 0 ){    // ボイス音量を外部の設定ファイルから取得
          _SoundManager->SetVoiceVolude( param );
      }
      param = ALStrToInt( sl->FindGetValue("DebugAddAttackPower") );
      if( param != 0 ){    // プレイヤーの攻撃力に設定ファイル分の数値を加算
          PlayerUnit::AddAttackDamage( param );
      }
  }
  
  「GameDef.txtの中身」
  VoiceVolume=50
  DebugAddAttackPower=150
  TextScale=1.5

こうしたファイルを利用することで、ゲームの微調整をソースコードのビルドなどを行わずに行うことができるようになります。

4. コリジョンの衝突領域を可視化したい

コリジョン判定がきちんと重なっているどうか確認したりする場合に、コリジョン範囲を可視化することが出来ます。

  ALCollisionManager::GetDefault()->ShowAll();

上記処理を実行すると、以降全てのコリジョンの範囲が可視化されます。

5. ALWidgetの状態変化などのログを確認したい

ALButtonWidgetをクリックしたがなぜか決定状態にならない場合などに、ボタンが本当にクリックされているのか確認する方法があります。

  ALWidget::SetLogLevel( ALLOG_LEVEL_DEBUG );

ログ出力する内容を制限することが出来ます。

  ALLOG_LEVEL_NONE    // ログ出力小
  ALLOG_LEVEL_ALWAYS
  ALLOG_LEVEL_ERROR
  ALLOG_LEVEL_WARNING
  ALLOG_LEVEL_NOTICE
  ALLOG_LEVEL_INFO
  ALLOG_LEVEL_DEBUG
  ALLOG_LEVEL_MAX      // ログ出力多

下へいくほどログが出力されるようになります。 ログ出力は全てのALWidgetのログが表示されるので、確認したいALWidgetがある場合はCaption()を設定しておくことをオススメします。 Caption()を設定している場合、ログ出力へCaption()も一緒に表示され、ALWidgetの判別が分かりやすくなります。

  ALWidget* widget = ALWidget::Create();
  widget->SetCaption( "TestWidget001" );

第3章 ALTable関連Tips

1. ALTableの内容が正しいか確認したい

ALTableで用意したデータのリストが正しいか確認する場合、現在の指し示しているレコードのフィールド全てをログ出力する関数が用意されています。 仮にキャラクターのプロフィールが記載されたデータのテーブルがある場合

  CharaDataTable* cdt = GetCharaDataTable();
  cdt->MoveRecordIndex( 30 );    // 30番目のキャラの情報へ移動
  cdt->DumpCurrentRecord();      // 現在のレコード情報をログへ出力

この場合は、30番目のデータだけがログへ出力されます。

また、テーブルの全データをログ出力する関数も容易されています。

  cst->CumpAllRecord();

この関数は全てのレコードごとのデータを一度に出力します。 キャラクターが全100人だとすると、100レコード分のDumpCurrentRecord()の出力が表示されます。

2. 固有IDでテーブルのレコードを移動させる

あるテーブルのインスタンスを複数個所で使いまわしていた場合、意図していないタイミングでテーブルのレコードが動いてしまい、意図していない値を取得してしまうことがあります。

  Sint32 ALTable::GetBookmark() const

を実行すると、その場でユニークな固有IDを生成し返します。 このユニークなIDを保存しておき、実際にデータを取得する直前にテーブルを再度移動させることで、バグを減らすことができます。 テーブルを移動させる場合は

  void ALTable::MoveBookmark(Sint32 bookmark)

関数を使用します。

3. 他の処理に影響を与えずにソートをする

同じインスタンスのテーブルをソートした場合、レコードの並び順が変化する可能性があります。 この時に別の箇所でテーブルを参照していた場合に、ソート結果によっては指し示す先が変わってしまうことがあります。 レコードの並びを変えずにソートする方法として

  ALTable * ALTable::Clone() const 

を利用する方法があります。 Clone()でコピーしたテーブルの方をソートすることで、Clone()の元になったテーブルに影響を与えることなく、ソートすることが出来ます。

4. ALTableをClone()する際の注意点

Clone()関数には2種類あり、それぞれ動作が変わります。 ■ ALTable::Clone() の場合

  // tblA[ 0, 1, 2, 3, 4, 5 ]とする
  ALTable* tblA = ALTable::Create();
  // Clone()すると、tblB[ tblA[0], tblA[1], tblA[2], tblA[3], tblA[4], tblA[5] ]になる
  ALTable* tblB = tblA->Clone();

tblBの中身は、tblAの要素への参照です。 その為tbllBの並び順を逆順にソートをした場合

  tblB[0] = tblA[5]
  tblB[1] = tblA[4]
  tblB[2] = tblA[3]
  tblB[3] = tblA[2]
  tblB[4] = tblA[1]
  tblB[5] = tblA[0]

となります。 その為tblBをソートしても、tblAには影響が及びません。 ※値を変更した時の注意点※ それぞれの要素は参照なので、tblB[0]に値を代入するとtblA[0]にも同じ値が代入されてしまいます。 逆順ソートしていた場合は、tblB[0]に値を代入すると、tblA[5]も書き変わります。

■ ALTable::CloneDeep() の場合

  // tblA[ 0, 1, 2, 3, 4, 5 ]とする
  ALTable* tblA = ALTable::Create();
  // CloneDeep()すると、tblB[ 0, 1, 2, 3, 4, 5 ]になる
  ALTable* tblB = tblA->CloneDeep();

tblBの中身は、tblAの中身と同じ物が代入され用意されます。 その為tblBの並び順を逆順ソートをした場合

  tblB[0] = 5
  tblB[1] = 4
  tblB[2] = 3
  tblB[3] = 2
  tblB[4] = 1
  tblB[5] = 0

となります。 tblAの方には影響が及びません。 ※値を変更した時の注意点※ それぞれの要素は値そのものなので、tblB[0]に値を代入してもtblA[0]の値には影響を及ぼしません。 逆順ソートしていた場合でも、tblB[0]に値を代入してもtblAには影響がありません。

5. ALTable::IsEof()がtrueになるタイミングについて

ALTableの要素を順に判定したい場合などに

  Sint32 bkmk = 123456789;    // 判定用の数値
  ALTable* tbl = ALTable::Create("TestTable.atb")
  bool isExist = false;
  // テーブルの要素を最後のレコードが判定されるまでループ
  for( tbl->First(); !tbl->Next() ){
      if( tbl->GetBookmark() == bkmk ){
          isExist = true;
          break;
      }  }

という書き方でループを回すことがあります。 for文の終了条件になっている、「IsEof()」関数の返り値が変わるタイミングに注意が必要です。 次のレコードへ移動できるかどうかを返す関数なのですが、最後のレコードに移動したらtrueを返すわけではありません。 次のレコードへ移動しようとして失敗した場合。つまり、次のレコードへ移動を試みた後に、trueを返すようになります。 テーブル「tbl」の要素が[ 0, 1 ]の2つだった場合に、

  1:  tbl->Next();    // tbl[0]からtbl[1]にカレントが移動
  2:  ALPrint( "%d", tbl->GetParam() );    // tbl[1]の中身を出力
  3:  // この時点ではまだIsEof()はfalseを返します。
      if( tbl->IsEof() )    return;
  4:  // tbl[1]からtbl[2]にカレントが移動しようとするが、存在しないのでtbl[1]に残る
      tbl->Next();    // このタイミングでIsEof()がtrueを返すように
  5:  ALPrint( "%d", tbl->GetParam() );    // tbl[1]の中身を出力
  6:  // Next()が最大要素数よりも多く呼ばれているので、Eof()はtrueを返します。
      if( tbl->IsEof() )    return;

ALTable::Next()が実行され、移動できなかった後に、IsEof()がtrueを返すようになります。 もしテーブルが最後まで判定されてるか判定する場合などで

  if( tbl->IsEof() )

と書いたりする場合は注意してください。 まだNext()などが実行されておらず、falseが返ってくる可能性があります。

第4章 サウンド関連Tips

1. ALSoundとALVoiceについて

Aqualeadでサウンドの再生をする場合、ALSoundクラスとALVoiceクラスを使うことになります。

  // 指定したサウンドのデータを生成
  ALSound* snd = ALSound::Create( BGM_001_ID );
  // サウンドを再生し、サウンドのコントローラーであるALVoiceを取得
  ALVoice voice = snd->Play();    // 再生と同時にALVoiceが生成され返される
  while( true ){
      ・・・    // 更新処理
  }
  voice->Stop();    // 登録されているサウンドを停止
  snd->Release();  // 生成したサウンドデータを解放

ALSoundクラスは、サウンドのデータそのものと再生する機能しか持っていません。 ALVoiceクラスは、ALSoundクラスで生成したサウンドデータを操作するクラスです。 サウンドの「停止」「一時停止」「フェードイン」などの操作はALVoiceを使って制御します。

生成したALSoundクラスのサウンドデータは、ALVoiceが再生終了するまでALVoiceがインスタンスを保持しています。 その為、ALVoiceを取得後すぐにALSoundクラスを解放をしたとしても、ALVoiceが最小カウントを増やしている為問題ありません。

2. ALSoundPlayer()について

Aqualeadのサウンド関連クラスには、もう一つクラスがあります。 ALSoundPlayerクラスです。 Aqualeadのサウンドは、このクラスを経由して再生されます。 このクラスに様々な設定をすることで、サウンド全体に効果を及ぼすことができます。 また、ALSoundPlayerをAddPlayer()関数で複数追加し、生成したALSoundクラスに登録するALSoundPlayerを分ける事で、全体に及ぼす効果を分けたりすることができます。

  // ALSoundPlayerにSE管理用のインスタンスを追加
  // ALSoundPlayerクラスは、起動時にAquqlead側で1つ必ず作られます
  ALSoundPlayer::AddPlayer();
  
  // BGM生成
  ALSoundPlayer* bgmPlayer = ALSoundPlayer::GetPlayer( 0 );    // 分かりやすいようにインスタンスを取得
  ALSound* bgmSnd = ALSound::Create( BGM_ID_01 );
  bgmSnd->SetPlayer( bgmPlayer );    // BGM用プレイヤーを設定
  // SE生成
  ALSoundPlayer* sePlayer = ALSoundPlayer::GetPlayer( 1 );
  ALSound* seSnd = ALSound::Create( SE_ID_01 );
  seSnd->SetPlayer( sePlayer );    // SE用プレイヤーを設定
  
  // BGMだけ停止
  bgmPlayer->StopAll();    // BGM_ID_01が停止し、SE_ID_01は再生され続けます
  // SEだけ音量UP
  sePlayer->SetVolume( sePlayer->GetVolume() + 1.0f );    // SE_ID_01の音量は上がりますが、BGM_ID_01の音量は変わりません

管理しているサウンドの「全停止」「全再生」「全一時停止」や「音量の一斉変更」といった操作に関することの他に「一度再生するサウンド数の制限」や「再生するサウンドの優先順位」なども設定できます。

3. 簡単にサウンドを再生したい

ALSound()やALVoice()、さらにはALSoundPlayer()など、必要な設定を記述せずにすぐにサウンドを再生したい場合は

  ALVoice ALSoundPlay( const char * name, ALNumber vol = 1.0f );
  ALVoice ALSoundPlay( Uint32 id, ALNumber vol = 1.0f );

というグローバルなインスタンスの関数が用意されています。 使用する場合は、ALSound.hを#includeする必要があります。 引き数に再生させたいサウンドを指定し音量を設定することで、その場でサウンドが再生されます。 再生が終わったあとは、サウンドデータを自動的に解放してくれるので、解放処理を書く必要もありません。 ALVoiceを返してくれるので、返り値のALVoiceを使い操作することも可能です。

第5章 ALNode関連Tips

1. GetPos()とGetWorldPos()の違い

Aqualeadで様々なオブジェクトの基底クラスとしてALNodeクラスがあります。 ALNodeクラスが座標を持ち管理していますが、座標を返す関数には大きく2種類あります。

  void ALNode::GetPos(ALVector *pos) const
  void ALNode::GetWorldPos(ALVector *pos) const

この2つの関数は、ALNode()の構成によって、返ってくる値が大きく変わる為注意が必要です。 GetPos()関数は「ALNodeに設定されている座標」 GetWorldPos()関数は「ALNodeが表示されているワールド座標」 という違いがあります。

  ALSprite* texA = ALSprite::Cretae( TEX_ID_01 );    // 画像Aを生成
  texA->SetPos( 400.0f, 300.0f, 0.0f );    // 座標を設定
  ALSprite* texB = ALSprite::Create( TEX_ID_02 );    // 画像Bを生成
  texB->SetParent( texA );    // texAを親として登録
  texB->SetPos( 50.0f, 50.0f, 0.0f );

というソースコードがあった場合に、texB->GetPos()で返ってくる値とtexB->GetWorldPos()で返ってくる値は、違う値が返ってきます。

  ALVector pos;
  texB->GetPos( &pos );
  ALPrintf( "%f,%f,%f", pos.x, pos.y, pos.z );    // 50.0, 50.0, 0.0と表示されます
  texB->GetWorldPos( &pos );
  ALPrintf( "%f,%f,%f", pos.x, pos.y, pos.z );    // 450.0, 350.0, 0.0と表示されます

texBのPosには「50.0f, 50.0f, 0.0f」としか設定されていませんが、画面上では「450.0f, 450.0f, 0.0f」の座標に表示されます。 これはtexAとtexBが「親子関係」の為、座標位置が加算された位置に表示されます。(親子関係については別項目で説明します) この「親子関係」を考慮せずにGetPos()で座標位置を取得し、衝突判定などを行うと意図していない結果になるので注意が必要です。 Get系関数だけでなく、Set系関数も同様です。 画面上での座標が知りたい場合は、GetWorldPos()関数を使用するようにしてください。

2. GetWorldPos()の注意点

GetWorldPos()関数で返される値は「更新前」の値が返される点に注意してください。 ALYield()で現在処理されているループが待機すると、各Aqualeadの更新処理が順次実行されます。 この時、座標位置の更新処理も実行され、計算結果がALNodeのメンバ変数へ格納されます。 GetWorldPos()関数は、この格納されている変数の値を返すので、更新前の値が返される事になります。 前回更新された位置ではなく、今現在の座標が知りたい場合は

  void ALNode::CalcWorldPos2D(ALVector2 *pos) const

を使用することで、その場で計算を行いその計算結果を返してくれます。 ※注意事項※ CalcWorldPos2D()関数は、その場で親の位置などを辿り再計算しなおす為、GetWorldPos()関数の方が高速に動作します。

3. グループと親子とは

ALNodeクラス同士を関連付ける仕組みがあります。 関連付けを行うと、ALNode解放の簡略化や描画位置設定の簡略化など、様々な利点があります。 関連付けは2種類存在しそれぞれ「グループ」と「親子」という関係に分けることができます。 ■ グループについて グループを作成する為には基準となる「オーナー」が必要になります。 ALNodeクラスは「オーナー」になることができませんが、「オーナー」へ登録することはできます。 「オーナー」はALGroupNodeクラス(継承したクラスでも可)である必要があります。

  ALGroupNode* pOwner = ALGroupNode::Create();
  ALSprite* pTex = ALNode::Create( TEX_ID_01 );
  pOwner->AddNode( pTex );    // オーナーに画像を追加し、グループ化
  pOwner->Destory();    // オーナーを解放することで、画像も一緒に解放されます

こうすることで、オーナーを解放するとグループ化したALNodeも一緒に解放され、個別に解放処理を書く必要が無くなります。 また設定よっては、グループを一つのノードとして描画することで、他のグループやノードと描画優先度が混ざらずに表示することができます。 ■親子について ALNodeは「オーナー」にはなれませんが、「親」になることができます。(ALGroupNodeも親になれます) 親子関係は、主に座標や色などALNodeが持つメンバ変数同士の加算したり、乗算したりすることが出来ます。 ある画像から10px横に動かした位置に別の画像を重ねたい場合

  // 画像texAからX方向に10pxだけずらした位置に、画像texBを表示する
  ALVector aPos;
  texA->GetWorldPos( aPos );
  ALVector newPos = ALVector( aPos.x + 10.0f, aPos.y, aPos.z );
  texB->SetWorldPos( newPos );

と記述する必要がありますが、親子関係を利用することで

  texB->SetParent( texA );    // texAを親に設定
  texB->SetPos( 10.0f, 0.0f, 0.0f );    // 10pxだけずらした位置に表示

と記述することができます。 ※注意事項※ 親子関係が付いたALNodeは、常に親の影響を受けます。 その為texAの位置を動かすと、同じ分だけtexBの位置も動いてしまいます。 その為、キャラクターにパワーアップエフェクトなどを表示する場合などに利用できます。 親子関係にしておくことで、キャラクターを動かすだけでエフェクトも同じ分だけ移動する為、キャラクター移動後にエフェクトを移動させる処理を記述する必要が無くなります。

4. Assemble()とAssembleMember()の違い

ALNodeをリソースから生成する場合

  ALNode * ALNode::Assemble(Uint32 fileid, bool ignorefunc=false)

という関数から生成します。もし、指定したリソースが複数のノードがグループ化されたものだった場合、ALGroupNodeクラスが返されます。 Aqualeadには、もう一つリソースからALNodeを生成する関数があります。

  void ALGroupNode::AssembleMember(const char *name)

このAssemble()とAssembleMember()の最大の違いは、オーナーノードにあります。 Assemble()は、新しくALNode(もしくはALGroupNode)を生成し、そのインスタンスをオーナーとして利用し返します。 AssembleMember()は、関数を呼び出したALNodeのインスタンスをオーナーとして利用します。

  // Assembleの場合
  ALNode* pNode;
  pNode->Assemble( MENU_ID_01 );
  ALNode* pNode02 = ALNode::Assemble( MENU_ID_01 );    // 勝手に生成されたALNodeインスタンスがオーナー
  // AssembleMemberの場合
  ALGroupNode* pGNode = ALGroupNode::Cast( pNode->FindNode(NODE_ID_02) );    // pNodeの中から同じIDのALNodeを探す
  pGNode->AssembleMember( MENU_ID_02 );    // pGNodeがオーナー
  
  // AssembleMemberの注意点
  ALGroupNode* pGnode2 = ALGroupNode::Create();
  // AssembleMemberは、自動的にALContainerWidgetを生成し、その下に指定IDのALNodeを生成します。
  // その為Crete()で生成したALGroupNodeに対してAssembeleMemberを実行すると、
  // ALContainerWidgetが1つ分余計に組み込まれます。
  // この場合は、Assembel()を実行してください。
  pGNode->Assemble( MENU_ID_02 );    // pGNodeがオーナー

5. 指定色になったか判定したい

徐々に赤色に変化させて、赤が1.0fになったら非表示にしたい場合などに、色の判定を行う関数があります。

  bool ALColor::IsEquals(const ALColor &col) const

引き数には、判定したいALColor構造体を渡してください。 一致した場合trueが返ってきます。

  ALNode* pNode = ALNode::Assemble( NODE_ID_01 );
  pNode->GetColor()->isEquals( ALColor( 1.0f, 0.0f, 0.0f, 1.0f ) );

6. FindNode()の検索範囲について

ノードの中から特定のノードのインスタンスを検索して返す関数があります。

  ALNode * ALNode::FindNode(Uint32 id) const

検索するノードの範囲は、グループ内です。親子関係ではない点に注意してください。

  ALGroupNode* pOwnerA = ALGroupNode::Create();
  ALNode* pNodeA = ALNode::Create();
  pNodeA->SetID( 1000 );
  pOwnerA->AddNode( pNodeA );
  
  ALGroupNode* pOwnerB = ALGroupNode::Create();
  ALNode* pNodeB = ALNode::Create();
  pNodeB ->SetID( 1001 );
  pOwnerB->AddNode( pNodeB );
  
  pOwnerA->FindNode( 1000 );    // pNodeAのインスタンスが返ってきます
  pOwnerA->FindNode( 1001 );    // pNodeBは、グループにいないのでNULLが返ってきます
  pOwnerB->FindNode( 1000 );    // pNodeAは、グループにいないのでNULLが返ってきます
  pOwnerB->FindNode( 1001 );    // pNodeBのインスタンスが返ってきます

7. 識別IDを設定する

FindNode()関数で特定のALNodeのインスタンスを取得し、個別に処理を行う事があります。 この時、ALSpriteEditorなどで作成したデータには、ALMakeCharID()でアルファベット4文字から生成されたID番号が設定されます。 手動で設定することも出来ます。

  void ALNode::SetID(const Uint32 id)

ID番号は数値であれば問題ありませんが、ALMakeCharID()から生成するのはオススメしません。 ログ出力を有効にした時に、設定したID値を見やすくする為です。

8. 表示されているか判定する

ALNode単体が表示されるか判定をする場合は

  bool ALNode::IsDisp() const

関数で判定することができます。 IsDisp()関数は、あくまで呼び出したALNode単体の、表示判定になります。 その為、外的要因によりIsDip()関数がtrueを返した場合でも表示されないことがあります。 親のALNodeが非表示設定になっている場合や、ALWidgetのインスタンスだった場合はSetEnable()がfalseになっているなど複数あります。 そうした外的要因も含めて、表示されるか判定するには

  bool ALNode::IsTotalDisp() const

関数を使用してください。

9. フェードの上に何か表示するには

画面全体を特定の色でフェードさせることができるALFadeクラスのフェード中に、フェードの色影響を受けないまま画像や文字を表示する場合があります。 例えばNowLoading画面や、読み込み中。演出中などです。 ALFadeクラスのSetDrawPrio()を利用することで実現することができます。

  ALFade::CreateInSuicide( 60, ALColor::Black, PRIO_FADE_ID )
  ALText* pText = ALText::Create();
  pText->SetString( "NowLoading" );
  pText->SetDrawPrio( PRIO_FADE_ID - 1 );    // テキストはフェードよりも手前に表示

DrawPrioの値は、1~255までの範囲内になるように設定してください。

10. サイズを好きに変える事が出来るウィンドウ

ゲームで汎用的に使用するウィンドウを作成する場合があると思います。 ウィンドウのサイズは場所によって変わる場合に、そのサイズごとにウィンドウの画像を用意する必要があります。 全サイズを用意するのは大変ですし、その分リソースの読み込む量も増えてしまいます。 そんな時に、ALPrimの機能を利用することで、一枚の画像で好きなサイズのウィンドウ背景を用意できるようになります。 画像はファイル名@3_3.pngの形で用意しする必要があります。 [0][1][3] [4][5][6] [7][8][9] という形に分割されて使用されます。 指定したウィンドウのサイズに合わせて [0][3][7][9]の4隅の画像はそのまま。 [1][8]はX方向だけ拡縮 [4][6]はY方向だけ拡縮 [5]はX・Y方向に拡縮 を行うことで、任意のサイズに対応します。 このような拡縮が発生する為、グラデーションの掛け方などを工夫する必要があります。 また実際のウィンドウサイズは、指定サイズ + 分割後のテクスチャサイズになります。

  // ウィンドウのサイズを指定
  float winSizeX = 400.0f;
  float winSizeY = 200.0f;
  // 背景画像を設定
  ALPrim* pWindow = ALPrim::Create();
  pWindow->LoadTexture( TEX_ID_01 );    // ウィンドウ背景画像読み込み
  // 分割後のテクスチャのサイズを取得
  float texX = pWindow->GetTexture()->GetWidth() * 1.0f;
  float texY = pWindow->GetTexture()->GetHeight() * 1.0f;
  // ALPrimへプリミティブを追加
  pWindow-> BeginAdd();
  // 画像の4隅分を含めた全体のウィンドウサイズを指定
  pWindow->AddNineSlice( ALFRect( 0.0f, 0.0f, winSizeX+texX*2.0f, winSizeY+texY*2.0f ) );
  pWindow->EndAdd();
  pWindow->SetPos( -texX, -texY );
  pWindow->Show();
  

11. HTML5でGetWorldPos()を使用する際の注意事項

ALNodeの画面上の座標を取得する際に、GetWorldPos()で座標を取得しますが、HTML5で取得しようとした場合に問題が発生します。 回避する為には、GetWorldPos2D()を使用してください。

12. ALIPMoveControllerの注意点

ALIPControllerは、開始値から終了値の値の遷移を行うクラスです。 ALIPMoveControllerは、「現在の値」から終了値までの遷移を行うクラスです。 この「現在の値」というのは、「実際にコントローラーが処理をする時の現在の値」ではありません。 「コントローラーが生成された時の現在の値」になります。 基本的には、生成と同時にコントローラー処理が実行される為問題になりませんが、ALDelayStartControllerを利用して、あるフレーム後に実行させる場合に、意図していない結果になることがあります。 例えば、1200フレーム後に急に現れる敵キャラクターがいる。 しかしプレイヤーが、消えている敵キャラクターを表示させるアイテムを使った場合などに挙動がおかしくなります。

  // 敵出現
  ALNode* enemy = ALNode::Create( ENEMY_01 );
  enemy->SetAlpha( 0.0f );    // 最初非表示
  enemy->Show();
  // フェードインコントローラー
  ALIPMoveController* fadeInController = ALIPMoveController::Create( enemy->FindProp("Alpha"), ALTP_TYPE_LINER, 1.0f, 60 );
  // 1200フレーム後にフェードイン
  enemy->AddController( ALDelayStartController::CreateSuicide( fadeInController ) );
  while( true ){
      ・・・    // 更新処理
      if( isUseItemID == ITEM_ID_SHOW_ALL_ENEMY ){    // 敵を全て表示するアイテムを使用した
          enemy->SetAlpha( 1.0f );    // 表示
          isUseItemID = -1;     // 使用アイテム情報を初期化
      }
      ALYield();
  }

もし、アイテムを使ったのが1200フレームよりも前だった場合

  ・コントローラーを追加した時のAlpha値「0.0f」。
  ・アイテムの影響でAlpha値「0.0f->1.0f」。
  ・1200フレーム経過、コントローラー処理実行。Alpha値[1.0f->0.0f]。    ←処理の前にコントローラーの生成時の値へ代入
  ・1260フレーム経過、Alpha値「1.0f」に。

という処理の流れになってしまい、敵キャラクターが点滅することになってしまうので、注意が必要です。 解決策する方法はいくつかあります。 表示非表示をAlphaでは無く、SetDisp()で行う。 アイテムが使われた段階で、コントローラーを削除する。 などの解決策が考えられます。

13. グループ関係の利点

グループの基準点になっているノードをオーナーと呼びます。 オーナーにノードを追加していくことで、様々な利点があります。

  1:オーナーの座標が基準になる
      複数の画像が組み合わさり一つの表示物として扱う場合などに、位置の変更処理が楽になります。
      オーナーの座標を変えることで、グループ全体の座標が同じ分だけ移動します。
  2:オーナーに追加した順番がそのまま描画優先度になる
      特に優先度を変更していない場合、ノードを追加した順番がそのまま描画優先度になります。
      注意点として、オーナー以外のノードの描画優先度の値は、グループの中だけに有効な値になります。
      オーナーはグループ外の優先度の影響を受けるので、グループの全ノードに影響がでます。
  3:グループ内のノードは一度に描画される。
      オーナーが描画されると続けてグループのメンバーが描画されます。
      この仕組みのおかげでグループのノードの間に、グループ外のノードが表示されてしまわないようになっています。
  4:オーナーが解放されると、メンバーも解放される。
      複数のノードが追加されたグループを解放する場合に、オーナーだけを解放することで、各メンバーのDestroy()やRelease()が実行されます。
      ソースコードが短く読みやすくなり、ノードの解放忘れも防ぐことができます。

      

第6章 スプライト関連Tips

1. ALImageとALTextureについて

画像を表示する場合に

  ALSprite::Cretae( TEXTURE_ID_001 );

などの関数を使い画像を生成します。 内部で、描画を担当するALTextureクラスと、画像データそのものを保持するALImageクラスが生成されます。 ALImageクラスは、メモリ上の画像データそのものを管理するクラスです。 画像のデータそのものを保持している為、画像の色の変更や他の画像の一部をコピーたりなど、画像データを操作することができます。 ALTextureクラスが描画に必要な各種設定を行い、描画自体も行います。 画像がパターン分割されている場合、パターン一つ一つのサイズ情報や座標などのデータは、ALTextureが保持しています。

2. パターンIDを利用したアニメーション

bmpやpng画像ファイルを、ALTextureConvツールでコンバートすることで、Aqualead内で使用できるテクスチャデータ型.atxファイルが出力されます。 この時、Image@3.pngといったように「@数値」をファイル名の最後につけてコンバートすることで、画像ファイルを分割することができます。 画像を分割するサイズは、画像全体のサイズを分割数で割ったサイズで分割されます。 その為、150*50の画像を@3で分割すると、50*50の画像3つに分割されます。 2枚目が80*50、3枚目が20*50といったように、サイズをバラバラに指定できないので、注意してください。 背景で炎が揺らめくアニメーションを再生するとします。 炎の揺らめきをコマ割りでそれぞれ画像で用意し、一枚の画像に連結しFireAnime@10.pngで画像を作成します。 コンバートして「FireAnime.atx」が作成されたとします。

  // 炎画像を生成
  ALSprite* pFireAnime = ALSprite::Create( "FireAnime.atx" );
  // PatternNoはALPropで値を保持しているので、ALPropとして取得できます
  ALProp patternNo = pFireAnime->FindProp( "PatternNo" );
  // ALPropなので、ALIPControllerに指定できます。
  // 30frmかけてパターンNOを0~9へ
  ALIPController* animeMove = ALIPController::Create( patternNo, ALIP_TYPE_LINER, 0, 9, 30 );

コントローラーで自動的に切り替わるようにすることで、毎フレームPatternNoを切り替える処理を書かなくても自動的にアニメーションが再生されます。

3. aedファイルを利用したアニメーション

画面の画像配置やボタン配置、レイアウトなどを定義しているaedファイルですが、アニメーションを組むこともできます。 アニメーションを組むには

  1:画像やテキストなどのノードを用意する
  2:各ノードにアニメの基準点となるキーを設定する
  3:設定したキーごとに必要な情報を設定する

この手順でアニメーションを作成していきます。 例えば ある画像Aがキー「0」フレームの時にScale「0.0」でキー「60」フレームの時にScale「1.0」 というアニメーションが組まれていた場合、60フレームかけて、等速で0.0から1.0へ大きくなるアニメになります。 このようなaedファイルをコンバートすることでモーションデータとして利用することができます。

  // 魔法アニメーションデータを読み込み
  ALArchive* animeData = ALArchive::Create( "MagicAnime.aar" );
  ALNode* magicEffect = ALNode::Assembele( MAGIC_ANIME_01 );    // アニメーション生成
  magicEffect->PlayMotion();    // アニメーション再生開始
  while( true ){    // 更新処理
      ALYield();
  }
  animeData->Release();    // 解放を忘れずに
  magicEffect->Destroy();

また複数のノードをそれぞれ別々にキーと値を設定したり、画像のPatternNoの分割を利用しても問題ありません。 これらを組あわせることで、複雑なアニメーションを作成することもできます。 複雑なアニメーションを再生する場合でも、プログラムの手順は大きく変わりません。 アニメーションを生成し、PlayMotion()で再生を開始するだけで作成したアニメーションが再生されます。

第7章 ALWidget関連

1. ウィジェットのStateについて

ウィジェットは現在の状態を表すStateというフラグを保持しています。 ステートの状態はそれぞれビットで管理されており、同時に複数の状態になります。

  typedef Uint16 ALWIDGET_STATE;
  static const ALWIDGET_STATE ALWIDGET_STATE_NORMAL = 0;            ///<なし
  static const ALWIDGET_STATE ALWIDGET_STATE_FOCUS = 1 << 0;        ///<フォーカスあり
  static const ALWIDGET_STATE ALWIDGET_STATE_OWNER_FOCUS = 1 << 1;  ///<親ウィジェットにフォーカスあり
  static const ALWIDGET_STATE ALWIDGET_STATE_DECISION = 1 << 2;     ///<決定状態
  static const ALWIDGET_STATE ALWIDGET_STATE_INVALID = 1 << 3;      ///<無効状態
  static const ALWIDGET_STATE ALWIDGET_STATE_OPEN = 1 << 4;         ///<オープン中
  static const ALWIDGET_STATE ALWIDGET_STATE_CLOSE = 1 << 5;        ///<クローズ中
  static const ALWIDGET_STATE ALWIDGET_STATE_OVER = 1 << 6;         ///<マウスが上にいる
  static const ALWIDGET_STATE ALWIDGET_STATE_PUSH = 1 << 7;         ///<ボタンが押されている途中
  static const ALWIDGET_STATE ALWIDGET_STATE_CHECKED = 1 << 8;      ///<チェックされている
  static const ALWIDGET_STATE ALWIDGET_STATE_ALL = 0xffff;

直接ウィジェットのステートをON/OFFすることはできません。 ウィジェットのステートを操作する為には、それぞれ

  void Open();
  void ClearOpen();
  void Close();
  void ClearClose();
  bool Decision();
  void ClearDecision();
  void SetFocus( bool focus = true );

といった関数を利用してステートのフラグを操作する必要があります。

2. ウィジェットのFocusとは

ウィジェットのStateにフォーカスという状態があります。

  static const ALWIDGET_STATE ALWIDGET_STATE_FOCUS = 1 << 0;        ///<フォーカスあり
  static const ALWIDGET_STATE ALWIDGET_STATE_OWNER_FOCUS = 1 << 1;  ///<親ウィジェットにフォーカスあり

この状態は指定のウィジェットに焦点が当たっている。選択中というような意味の状態になります。 このフォーカスという状態を利用することで、複数の選択肢の中から、選択しているウィジェットが分かるようになります。 ウィジェットがフォーカス状態になる為には、自身のオーナーのフォーカスがTrueになっていることが条件になります。

RPGゲームなどでアイテムの一覧画面などを開いているとします。 ゲームパッドの下キーを押すと、カーソルが一つ下がりカーソルが指しているアイテムの文字色が変わる。 このような処理を実装する場合などに利用できます。

  bool IsFocus() const;
  bool IsOwnerFocus() const;

今フォーカスが当たっているかどうかは、IsFocus()で判定できますが、現在選択しているたった一つのウィジェットを取得したい場合は、注意が必要です。

  [オーナー]
      ↓→→→→→→→→→→→→┐
      ↓                                    ↓
  [コンテナA]                        [コンテナB]
      ↓→→→→→┐                  ↓→→→→→┐
      ↓               ↓                  ↓               ↓
  [ボタンA]       [ボタンB]         [ボタンC]       [ボタンD]

このようなグループ構成だった場合に選択しているボタンの色を変えるなどの処理をするとします。 ボタンを押すと、各ウィジェットのステートは以下のように変化します。

  ①:[ボタンA]を選択
  [コンテナA]                                                           [コンテナB]
  OwnerFocus: true                                                  OwnerFocus: true
          Focus: false -> true                                       Focus: false

  [ボタンA]                           [ボタンB]                      [ボタンC]                 [ボタンD]
  OwnerFocus: false->true    OwnerFocus: false->true    OwnerFocus: false    OwnerFocus: false
          Focus: false->true             Focus: false                     Focus: true             Focus: false
       Decision: false->true          Decision: false                  Decision: false         Decision: false

  ②:①の後に[ボタンC]を選択
  [コンテナA]                                                           [コンテナB]
  OwnerFocus: true                                                  OwnerFocus: true
          Focus: true -> false                                       Focus: false -> true

  [ボタンA]                           [ボタンB]                       [ボタンC]                         [ボタンD]
  OwnerFocus: true->false      OwnerFocus: true->false   OwnerFocus: false->true    OwnerFocus: false->true
          Focus: true                        Focus: false                    Focus: true                     Focus: false
       Decision: true->false            Decision: false                Decision: false->true         Decision: false

[ボタンA]の後に[ボタンC]を押すと、[ボタンA]と[ボタンC]それぞれのFocusがtrueになります。 IsFocus()だけで判定した場合、選択中の表示になるボタンが同時に2つ存在することになってしまいます。 その為、IsOwnerFocus()でも判定を行うことで、現在選択中のウィジェットを判定することができます。

3. ウィジェットのFocusの初期状態

各ウィジェットのFocusの状態フラグの初期状態についてです。 あるオーナー[オーナーA]にあるボタン[ボタンA]と[ボタンB]と[ボタンC]を追加します。 この時に、各ボタンのFocus状態は[ボタンA]のみFocus状態フラグがONになります。 ウィジェットはオーナーに追加された時に、まだ他にウィジェットが追加されていない場合、自動的にフォーカスが設定されます。 その為、[ボタンB][ボタンC]のFocus状態はOFFの状態で追加されます。

意図的に何も選択されていない状態を作りたい場合は、意図的にFocus状態をOFFにする必要があります。

4. ウィジェットのdefaultOwner

あるウィジェットが決定状態になる為には、そのウィジェットのフォーカスとオーナーフォーカス状態フラグが両方立っている必要があります。 しかしウィジェットをALContainerWidgetなどに追加せずに利用することもあると思います。 画面上でクリックしたら先に進む場合などに、見た目を設定していない画面と同じ大きさのALButtonWidgetを生成した場合などです。

  ALButtonWidget* clickWidget = ALWidget::Create( BUTTON_SKIN_ID );
  clickWidget->SetSize( 800.0f, 600.0f );  // 画面サイズと同じ
  clickWidget->AddFitCollision();
  while( !clickWidget->IsDecision() ){    // 決定状態になるまで待機
      ALYield();
  }

このような場合の時に、clickWidgetが決定状態になる為には、オーナーのフォーカスフラグもONになっている必要があります。 しかしclickWidgetのオーナーは指定していません。 その場合でもクリックしたら決定状態になります。 これは、clickWidgetのオーナーにDefaultOwnerというContainerWidgetが自動的に設定される為です。 引き数に別のウィジェットを渡したり、SetdDfaultOwnerでインスタンスを変えることができます。

  void ALWidget::SetDefaultOwner(ALContainerWidget *wid)
  ALContainerWidget * ALWidget::GetDefaultOwner()

defaultOwnerは最初はNULLが代入されている為、ゲームの初期化処理などで、defaultOwnerを設定する必要があります。 自分で用意しない場合は、SystemOwnerを登録してください。 SystemOwnerは、Aqualeadが用意したALContainerWidgetです。 SystemOwnerのインスタンス自体は、変更できませんが、描画優先度を変更するなどの設定の変更は行う事ができます。

  ALContainerWidget * ALWidget::GetSystemOwner()

5. スキンIDで動作のテンプレートを設定する

ALWidgetにスキンIDとスキン処理を設定することで、スキンIDごとに動作や設定を生成時に設定することが出来ます。 ボタンが押された時にアニメーションをする。 スクロールバーに必要な設定を行う。 など、様々な使い方ができます。 スキンを設定する為には

  1:スキン設定初期化処理関数を用意。
  2:ALWidget状態変化処理関数を容易。
  3:スキンクラスを生成。初期化処理関数と状態変化処理関数を引き数に渡し設定する。
  4:スキンIDとスキンクラスを関連付ける。

という手順になります。

  // 初期化処理関数
  void SkinInitFunc( ALFuncWidgetSkin* self ){
      ALWidget* widget = self->GetOwnerWidget();
      widget->AddFitCollision();    // コリジョン判定とWidgetサイズを揃える
      widget->SetUseReleaseClick( true );    // クリックを離した時に状態を変える
  }
  // 状態変化処理関数
  void SkinChangeFunc( ALFuncWidgetSkin* self ){
      ALWidget* widget = self->GetOwnerWidget();
      if( widget->IsDecision() ){    // 決定状態の時のみ処理を実行する
          ALProp posY = widget->FindNode( "Pos.y" );
          ALIPController* controller = ALIPController::CreateSuicide( posY, ALIP_TYPE_SIN, widget->GetPosY(), widget->GetPosY()+10, 5, false, true );
          // Y方向にすばやく上下し、ボタンを押したようなアニメーションを再生。
          widget->AddController( controller );
      }
  }
  void Main(){
      Uint32 SKIN_ID = 0;
      // スキンクラス生成
      ALWIdgetSkin* pSkin = ALFuncWidget::Create( SkinInitFunc, SkinChangeFunc );
      // スキンIDとスキンクラスを紐付ける
      ALWidget::EntryReleaseSkin( SKIN_ID, oSkin );
      // ALButtonWidgetをスキンを設定して生成する
      ALButtonWidget* btnWidget = ALButtonWidget::Create( SKIN_ID );
      ・・・    // 以下略
  }

プログラム中でスキンを定義しておくことで、ALSpriteEditorなどのツールで作成した場合に、ツールからスキンIDを設定しておくこともできます。

6. スクロールバー用のスキンを設定する

スクロールバーを使用する場合は、スクロールバー用の処理を設定する必要があります。

  1:スクロールバーのノブを生成する関数を用意する。
  2:スクロールバーの背景を生成し、ノブも生成する関数を用意する。
  3:スキン設定初期化処理関数を用意。
  4:初期化処理関数からスキンクラスを生成
  5:スキンIDとスキンクラスとスクロールバーのインスタンスを関連付ける

リソース関連では、スクロールバー用の画像が必要になります。

  ・スクロールバーの背景画像
  ・スクロールバーの上下を示すアロー画像
  ・スクロールバーのつまみを表すノブ画像

画像として用意するので、スクロールバーのノブの画像に変えたりすることで、オリジナルのスクロールバーを作成することができます。 またスクロールバーの長さなどは動的に変化する為、画像は@3で分割されるものを用意してください。 あまり大きな画像でスクロールバーを用意すると、他の表示物などと被ってしまうことが考えられるので、できるだけ小さい画像を用意するのが好ましいと思われます。

  // ノブサイズ更新処理
  void KnobResizeFunc( ALFuncWidgetScrollThumb* self, Uint32 _length ){
      ALSprite* sp = ALSprite::Creaete( KNOB_TEXTURE );
      Uint32 minSize = sp->GetTexture()->getHeight() * 2;
      Uint32 length = ALMax( _length, minSize );    // ノブも含めた全体の長さ
      sp->Destroy();
      if( _length != 0 && (self->GetNodeCount() == 1 || self->GetLength() != length) ){    // 初回処理
          self->ClearNode();
          self->SetLength( length );
          // ノブの上部生成
          ALSprite* pTopSp = ALSprite::Create( SCROLL_KNOB_TEXTURE );    // ノブ画像生成
          pTopSp->SetParent( self );
          self->AddReleaseNode( pTopSp );
          pTopSp->Show();
          Sint32 topHeight = pTopSp->GetTexture()->GetHeight();
          // ノブの下部生成
          ALSprite* pBottomSp = pTopSp->Clone();
          pBottomSp->SetPatternNo( 2 );
          Sint32 bottomHeight = pBottomSp->GetTexture()->GetHeight();
          pBottomSp->SetY( 1.0f * ( length - bottomHeight ) );
          // ノブの中央生成
          ALSprite* pMiddleSp = pTopSp->Clone();
          pMiddleSp->SetPatternNo( 1 );
          Sint32 middleHeight = pMiddleSp->GetTexture()->GetHeight();
          pMiddleSp->SetY( 1.0f * middleHeight );
          pMiddleSp->SetScaleY( 1.0f * ( length - 1.0f * topHeight - 1.0f * bottomHeight ) / ( 1.0f * middleHeight ) );
      }
      ALWidgetScrollBar* scrollBar = self->GetOwnerScrollBar();
      if( scrollBar != NULL ){
          ALSprite::Cast( scrollBar->FindNode(1) )->SetPatternNo( _length != 0 ? 0 : 1 );
          ALSprite::Cast( scrollBar->FindNode(2) )->SetPatternNo( _length != 0 ? 0 : 1 );
      }
  }
  
  // スクロールバー生成
  void ScrollBarBaseCreateFunc( ALFuncWidgetScrollBar* self ){
      ALWidget* owner = self->getOwner();
      self->ClearNode();
      // 背景上部生成
      ALSprite* pBgSpUp = ALSprite::Create( SCROLL_BG_TEXTURE );
      pBgSpUp->SetParent( owner );
      owner->AddReleaseNode( pBgSpUp);
      pBgSpUp->Show();
      // アロー画像生成
      ALSprite* pUpSp = pBgSpUp->Clone();    // 上方向画像
      pUpSp->LoadTexture( SCROLL_ARROW_UP_TEXTURE );
      pUpSp->SetID( 1 );
      ALSprite* pDownSp = pBgSpUp->Clone();  // 下方向画像
      pDownSp->LoadTexture( SCROLL_ARROW_DOWN_TEXTURE );
      pDownSp->SetID( 2 );
      ALTexture* pArrowTex = pDownSp->GetTexture();
      pDownSp->SetY( 1.0f * ( owner->GetHeight() - pArrowTex->getHeight() ) );
      // アローの判定サイズを設定
      pArrowTex = pUpSp->GetTexture();
      self->SetUpDownButtonSize( pArrowTex->GetHeight() );
      // 背景の位置をアロー画像分下げる
      Uint32 arrowSize = self->GetUpDownButtonSize();
      pBgSpUp->SetY( 1.0f * arrowSize );
      // 背景下部生成
      ALSprite* pBgSpDown = pBgSpUp->Clone();
      pBgSpDown->SetPatternNo( 2 );
      pBgSpDown->SetY( 1.0f * ( owner->GetHeight() - pBgSpDown->GetTexture()->GetHeight() - arrowSize ) );
      // 背景中央部生成
      ALSprite* pBgSpCenter = pBgSpUp->Clone();
      pBgSpCenter->SetPatternNo( 1 );
      pBgSpCenter->SetY( 1.0f * ( pBgSpUp->GetTexture()->GetHeight() + arrowSize ) );
      Sint32 centerHeight = owner->getHeight() - pBgSpUp->GetTexture()->GetHeight() - pBgSpDown->GetTexture()->GetHeight() - (arrowSize*2);
      pBgSpCenter->SetScaleY( (1.0f * centerHeight) / (1.0f * pBgSpCenter->GetTexture()->GetHeight()) );
      // スクロールバーの太さを設定
      self->SetThick( pBgSpUp->GetTexture()->GetHeight() );
      // スクロールバーを配置位置へ移動
      if( self->GetPlace() == ALWIDGET_SCROLL_BAR_PLACE_LEFT ){    // 左側へ配置するなら
          self->SetX( 0 );
      }
      else{    // 左以外の場合は、右端に配置
          self->SetX( 1.0f * ( owner->GetWidth() - self->GetThick() ) );
      }
      // ノブを生成
      ALFuncWidgetScrollThumb* pKnob = ALFuncWidgetScrollThumb::Create( self, self->GetThick(), 0, KnobResizeFunc );
      // ノブを登録
      self->SetThumb( pKnob );
  }
  
  // スキン処理
  void SkinInitFuncCellListScrollBar( ALFuncWidgetSkin* self ){
      ALCellListWidget* cellList = ALCellListWidget::Cast( self->GetOwnerWidget() );
      cellList->UseScrollBar();    // スクロール使用
      cellList->SetSwipe( true );  // スワイプでスクロール操作を有効化
  }
  
  void Main(){
      // スキン処理生成
      ALWidgetSkin* pSkin = ALFuncWidgetSkin::Create( SkinInitFuncCellListScrollBar );
      // スクロールバー生成
      ALWidgetScrollBar* pScroll = ALFuncWidgetScrollBar::Create( ScrollBarBaseCreateFunc );
      // スキンIDにスキンを登録
      ALWidget::EntryReleaseSkin( SKIN_ID_SCROLL_BAR, pSkin, NULL, pScroll );
  }

スクロールの位置や動作など細かく設定することができますが、その動作を実現する為の処理を記述する必要があります。

7. ウィジェットにOpenMotionが設定されている場合の注意点

ウィジェットが表示される時に、アニメーションを演出として設定することができます。 例えば、表示される時にフェードインする。少し左からスライドインしてくる。膨らむように少し拡大する。 アニメーションを用意することで、複雑なアニメーションも再生できます。

  ALButtonWidget* button = ALButtonWidget::Create( RESORCE_ID_BUTTON_WIDGET );
  button->SetOpenMotionID( BUTTON_WIDGET_MOTION_ID );
  button->Open();    // 開いた状態へ
  while( true ){
      ALYield();
  }

OpenMottionがフェードインさせる場合などに、ウィジェットが表示されないことがあります。 ウィジェットのステートがOpenへ切り替わらずに、OpenMotionが実行されない為アルファ値が変動せずに、0のままになってしまうことがある為です。

8. 衝突判定の優先度と描画優先度

描画する時に、ある画像の上から描画をするか、ある画像の下に描画されるかは、描画優先度で決定されます。 この描画優先度は意図的に変える事ができます。

  ALNode::SetDrawPrio( ALFixNum prio );

値が小さいほど手前に、大きいほど奥に表示されます。 または

  ALNode::SetDrawPrioOffset( ALFixNum prioOffset );

ALNode::GetBaseDrawPrio()で取得できる基準点からのオフセットで優先度が設定されます。 この描画優先度の値は、描画だけでなく衝突判定の優先度にも利用されます。 同じ優先度の場合は、ALGroupNodeに追加された順番が早い方が優先されます。

9. ウィジェットの有効/無効と表示/非表示

あるボタンだけを非表示にするが、隠しボタンとしてクリックだけさせたい。 表示はされているか、クリックしても反応しないようにしたい。 条件によって、ボタンを無効化したいことがあると思います。 Hide()、Invalid()、Enable()などを設定することで、ボタンの有効/無効や表示/非表示をコントロールできます。

  // 表示に関する関数
  void ALNode::SetDisp( true );  // 表示をON/OFF
  void ALNode::Show()   // 表示。SetDisp(true)と等価
  void ALNode::Hide()    // 非表示。SetDisp(false)と等価
  // 有効無効に関する関数
  void ALWidget::SetInvalid( bool valid = true )    // trueで無効
  // 表示と有効に関する関数
  void ALUpdater::SetEnable( bool enable )    // trueで表示と有効

表示だけをコントロールする場合は、SetDisp()。 動作をコントロールする場合は、SetInvalid()。 表示も動作も一度にコントロールする場合は、SetEnable()。 を使用することで、コントロールすることができます。 注意が必要なのは、それぞれ関数の定義されているクラスが違うという点です。 その為SetEnable(false)で非表示にした後に、SetDisp(true)で表示しても、正しく表示されません。 ALNodeの表示設定は書き変わりましたが、ALUpdaterbの更新が無効になっている為です。 この場合は、きちんとSetEnable(true)でALUpdaterの更新を有効化する必要があります。

動作を無効化するということは、ウィジェットのステートの変化の更新を停止する。ということです。 ステートが変化しない為、実質ウィジェットの動作を停止することができます。

SetEnable()を実行した時の動作は、各描画の更新処理と内部処理のUpdateFuncの動作をSleep()で停止します。 描画と内部処理の更新がSleep()で一時停止する為、表示と動作が一時停止します。

10. ウィジェットのClone()

同じグループ関係や親子設定などを使いまわす時に、それぞれのウィジェットにもう一度設定しなくても、Clone()を使うことで、設定をひきついだインスタンスを新たに作ることができます。

  // オーナー生成
  ALGroupNode* owner = ALGroupNode::Create();
  // クローン元ウィジェット生成
  ALWidget* wd1 = ALWidget::Create();
  owner->AddNode( wd1 );
  wd1->SetParent( owner );
  wd1->Show();
  // クローンウィジェット生成
  ALWidget* wd2 = wd1->Clone();
  wd2->SetX( 100.0f );    // 位置を調整

Clone()で生成されたwd2は、Clone()された時点で、SetParent(owner)とAddNode(wd2)がされた状態になっています。 またClone()元のwd1とは違うインスタンスになるので、wd2の座標を変更したりしても、wd1には影響を及ぼしません。

11. スクロールバーのノブの位置を変更する

画面の遷移をしたあとに、スクロールの位置を覚えておきたい場面があります。 ゲームキャラのステータス画面を表示して、キャラ一覧画面に戻った場合などです。 キャラクターが多くスクロールバーが長い場合に、先頭から再びスクロールさせると、非常に面倒になります。 このような場合には、画面を遷移する前に現在のスクロールの位置を覚えて、戻ってきたらその位置にスクロールさせることで、実現できます。

  Uint32 listIdx = 0;  // グローバル変数
  ALNumber listPos = 0.0f;
  
  // キャラのリスト
  ALCellListWidget* pList = ALCellListWidget::Create( CHARA_LIST_MENU_ID );
  if( listIdx > 0 ){    // スクロール位置の設定
      pList->SetTopIndex( listIdx );
      listIdx = 0;  // 初期化
  }
  else if( listPos > 0.0f ){
      pList->SetTopPos( listPos );
      listPos = 0.0f;  // 初期化
  }
  while( true ){
      ・・・  // 画面遷移判定など
  }
  // 現在のスクロール位置を覚える
  listIdx = pList->GetTopIndex();    // CellListの中で今選択されているCellの番号
  listPos = pList->GetTopPos();    // CellListのスクロールの一番上からの位置

実際には、TopIndexかTopPosのどちらか一方しか使わないと思います。 それぞれ挙動が少し変わります。 SetTopInedx()は、CellListの指定番号目のCellが一番上に来るようにスクロール位置を設定します。 SetTopPos()は、スクロールの一番上の地点から、指定した位置を設定します。 その為SetTopPos()では、リストの中身が中途半端に表示され切れる場合があります。

12. FindNode()でノードが見つからない

ALSpriteEditorなどでメニューやアニメーションを作成し、aedファイルからノードを生成することができます。 作成したノードのグループの中から、画像を差し替えたい場合や一部を非表示にしたい場合などに、FindNode()を利用することで、ノードのインスタンスを取得することができます。

  <Node_SP00>
    <ObjectType Type="String">
      <Value>SP</Value>
    </ObjectType>
    <Caption Type="String">
      <Value>Sprite00</Value>
    </Caption>
    <Texture0Name Type="Prop">
      <Value>Texture/testTexture@2.png</Value>
    </Texture0Name>
  </Node_SP00>

といったような形でノードごとに<Node_****>という形のタグでaedファイルに出力されます。 実際にノードが生成された時にグループの各ノードは、*の4文字を数値化したIDが設定された状態で生成されます。 この4文字の文字列から数値を求めるには、ALTypes.hに定義されている

  #define ALMakeCharID( c1, c2, c3, c4 ) static_cast<Uint32>(( (c1) | ((c2) << 8) | ((c3) << 16) | ((c4) << 24) ))

を利用することで、生成されたノードに設定されているIDを求めることが出来ます。 求めたID番号でFindNode()を実行すればインスタンスを取得することができるはずですが、確かにaedファイルに記述されていて、文字列も間違っていないのにFindNode()が失敗することがあります。 原因の一つとして考えられるのが、aedファイルの中のタグの定義順番が考えられます。

  <Node_ND00>
    <ObjectType Type="String">
      <Value>ND</Value>
    </ObjectType>
    <Caption Type="String">
      <Value>DummyNode</Value>
    </Caption>
  </Node_ND00>
  <Node_SP00>
    <ParentNodeID Type="Prop">
      <Value>ND00</Value>
    </ParentNodeID>
  </Node_SP00>

<ParentNodeID Type="Prop">タグに設定されたノードはそのノードの親になります。 その為、座標や中央座標などの実際の値は、親の値+自身の値になります。 上記の例の場合、ND00がSP00よりも下に書かれていた場合でも、問題なくコンバートされノードも生成することができます。 しかし、全ノードの中から指定されたIDのノードを検索する時の順番などの関係で、上手く検索できない場合がある為、親子関係を設定した場合はタグの記述位置に気をつける必要があります。

13. CellListWidgetの範囲外に出たCellについて

ウィジェットのリストを生成する場合は、ALCellListWidgetを生成して、Cellを追加していくことでリストを作ることができます。 縦や横だけといった1次元のリストのほかに、グループ関係と親子関係を使用することで縦横両方の2次元のリストを作ることもできます。 例えば、100*100の大きさのアイテムのアイコンが横に最大3個並ぶリストを作成するとします。 アイコンをクリックすると、そのアイテムの詳細情報画面へ遷移します。

  □□□
  □□□
  □□

このような見た目になるイメージです。

  // 基準になるアイコンを生成
  ALButtonWidget* pBaseWidget = ALButtonWidget::Create();
  pBaseWidget->SetSize( 100, 100 );
  // リスト本体を生成
  Uint32 _ListWidth = 3;    // リストの横方向の最大表示数
  ALCellListWidget* pCellList = ALCellListWidget::Create();
  pCellList->SetCellSize( pBaseWidget->GetWidth() * _ListWidth, pBaseWidget->GetHeight() );    // 1行の範囲
  
  // アイテムのデータが記載されたテーブルからリストを作成します
  ItemDataTable* idt = ItemDataTable::Create( "ItemDataTable.atb" );
  // アイテムデータの全体数をアイコンの横方向の表示数で割ることで、全体の縦の行数を求める
  Uint32 _ListHeight = (idt->GetRecordCount() + _ListWidth - 1) / _ListWidth;
  // リストの内容を生成
  idt->First();
  for( Uint32 nowH = 0; nowH < _ListHeight; nowH++ ){    // 行を生成
      while( !idt->IsEof() ){
          if( idt->IsDisable() )    idt->Next();     // 表示が無効設定されていたらスキップ
          else    break;    // 有効ならwhileループを抜ける
      }
      if( idt->IsEof() )    continue;    // すでに全データを判定し終わっているならforループを進める
      // アイテムデータの判定が終わったので、実際にアイコンを追加
      ALContainerWigdet* cell = pCellList->CreateCell();    // まずはリストにCellを追加する
      // 横方向のアイコンを追加していく
      for( Uint32 nowW = 0; nowW < _ListHeight; nowW++ ){
          if( idt->IsDisable() )    idt->Next();     // 表示が無効設定されていたらスキップ
          if( idt->IsEof() )    continue;    // すでに全データを判定し終わっているならforループを進める
          ALButtonWidget* pButton = pBaseWidget->Clone();
          ALSprite* pIconSprite = ALSprite::Create( idt->GetTextureID() );    // アイテム画像を生成
          pButton->AddReleaseNode( pIconSprite );
          pIconSprite->SetParent( pButton );
          // Cellへ登録
          cell->AddReleaseNode( pButton );
          pButton->AddX( pButton->GetWidth() * nowW );    // アイコンの横幅 * 現在の追加数 分だけX方向へ移動
          pButton->SetTag( idt->GetItemID() );    // 押したボタンの区別用にアイテムIDをタグに設定
          // アイコンを追加したので次のレコードへ
          idt->Next();
      }
  }

cellはALContainerWidgetなので、ボタンや他のウィジェットもグループとして追加することができます。 ALContainerWidgetのグループ関係を利用して、横方向の要素を作成し手いる為、GetNode()やFindNode()でノードを取得することができます。 なお範囲外にあるCellはDraw処理が呼ばれない為、その為描画の負担が軽くなります。

14. GrobalLockの注意点

演出のアニメーションの再生中などに、余計なボタンなどをクリックされても動作させたくない場合などに

  void ALWidget::GrobalLock()

を使うことで、クリックされた場合に状態が切り替わるのを、防ぐことができます。 ただし注意が必要な場合があります。 例えばダイアログなどを表示する場合です。 ダイアログを表示している間は他のボタン類は操作できなくする場合が多いと思います。 そのような場合にGrobalLcok()を使用すると、ダイアログを閉じることができなくなる場合があります。 ゲーム画面であるリストの一覧画面から何かをクリックすると、そのクリックしたものの情報ダイアログが表示される。 というような場合に

  // ダイアログを表示
  void ShowDialog( const char* message ){
      // 余計なボタンを押させないようにロック
      ALWidget::GrobalLock();
      ALNode* window = ALNode:: Assemble( DIALOG_NODE_ID );
      ALText* text = ALText::Cast( window->FindNode(ALMakeCharID('T','X','A','A')) );
      text->SetString( message );
      while( true ){
           if( window->GetDecisionWIdget() != NULL )    break;    // ボタンが押されたらループを抜ける
           ALYield();
      }
      // ダイアログを閉じたのでロック解除
      ALWidget::GrobalUnlock();
      window->Destroy();
  }
  void Main(){
      ・・・    // アイテムリストを表示したりボタンをクリックする処理
      if( clickButtonID > 0 ){    // アイテムIDが代入されているなら
          ItemDataTable* idt = GetItemDataTable();
          idt->MoveItemID( clickButtonID );
          // 情報ダイアログを表示
          ShowDialog( idt->GetText() );
      }
  }

このような場合に、ダイアログを閉じることができなくなり操作が止まってしまいます。 ・ダイアログを閉じるのにボタンを押す必要がある場合 ・ダイアログのボタン押し待ちループの前に、GrobalLock()されたままループしている場合 原因はGrobalLock()の効果が全てのウィジェットに適応される為、ダイアログのボタンもステートが変化せず、決定ボタンにならない為です。 他のウィジェットの動作を停止させたい場合は、「複数Sceneを使ったダイアログ」の方法で動作を管理することをオススメします。

15. 複数Sceneを使ったダイアログ

演出のアニメーション中や、会話イベント中、ダイアログ表示中など、ある特定の処理の間だけ、他の処理を停止させておきたい場合があります。 処理を停止させるには、Sleep()を実行することで更新処理を停止させることができます。 しかし、ゲーム中のあらゆる処理をSleep()させるのは非常に手間がかかりますし、Awake()で再開させる際にAwake()の実行抜けがあると一部の処理が停止したままになってしまいます。 そんな時は

  void ALScene::PushSleepOthers()
  void ALScene::PopAwakeOthers()

を使うことで一度に全ての更新処理を停止と再開をすることができます。

  // ダイアログの更新処理を実装
  void dialogUpdateFunc( ALNode* self ){
      ALContainerWidget* window = ALContainer::Cast( self );
      // ボタンが決定されるまで待機
      while( window->GetDecisionWidget() == NULL )    ALYield();
      ALScene* dialogScene = window->GetScene();
      dialogScene->PopAwakeOthers();    // ダイアログシーン以外を全員再開
      self->Sleep();    // 終了の変わりに自身をSleep()
  }
  // ダイアログを表示
  void ShowDialog( const char* mes ){
      // GrobalLockしているとダイアログの操作が出来なくなる為解除
      bool isLocked = ALWidget::IsGrobalLock();
      if( isLocked )    ALWidget::GrobalUnlock();
      // ダイアログ用シーン生成
      ALScene* dialogScene = ALScene::Create();
      dialogScene->PushSleepOthers();             // ダイアログシーン以外を停止
      ALScene::PushSetDefault( dialogScene );    // ダイアログシーンをDefaultへ設定し、他のシーンを一時退避
      // ダイアログを生成
      ALContainerWidget* window = ALContainerWidget::Cast( ALNode::Assemble(DIALOG_MENU_DATA_ID)):
      ALText* text = ALText::Cast( wwindow->FindNode( ALMakeCharID() ) );
      text->SetPrintf( ALMakeCharID('T','E','X','T') );
      window->SetUpdateFunc( dialogUpdateFunc );    // ダイアログ用更新処理登録
      
      ALScene::PopDefault();
      // ダイアログのボタンが押されるまで待機
      while( !window->IsSleep() )    ALYield();
      // ダイアログシーンを開放
      dialogScene->Release();
      if( isLocked )    ALWidget::GrobalUnlock();
  }

大まかな手順としては

  1:新しいシーン[B]を作る
  2:シーン[B]以外をSleep()させる
  3:元のシーン[A]をスタックへ一時退避し、シーン[B]をデフォルトへ
  4:ダイアログのボタン待機ループに入る前に、シーン[B]をスタックから削除
  5:ダイアログのボタンが押されたら、シーン[B]以外をAwake()させる。
  6:シーン[B]を解放

という流れになります。