第9回

prev slash top slash next

第9回:マイクロスレッド

マイクロスレッド

今回はマイクロスレッドです。
マイクロスレッドを使えば複雑なプログラムを簡単に書けるようになります。

前回までの講座では、全方位弾を交互に撃つという状態になっていたかと思います。

#東方弾幕風
#Title[関数]
#Text[関数]
#ScriptVersion[2]
script_enemy_main
{
 let imgBoss = "script\img\ExRumia.png";
 let count = 0;
 @Initialize{
  SetX(GetCenterX);
  SetY(GetClipMinY+120);
  SetLife(2000);
  SetTimer(60);
  LoadGraphic(imgBoss);
  SetTexture(imgBoss);
  SetGraphicRect(0, 0, 63, 63);
 }
 @MainLoop{
  SetCollisionA(GetX, GetY, 24);
  SetCollisionB(GetX, GetY, 24);
  if(count==20)
  {
   AllroundShot(GetAngleToPlayer, BLUE01, 16);
  }
  if(count==40)
  {
   AllroundShot(rand(0, 360), RED01, 32);
   count=0;
  }
  count++;
  yield;
 }
 @DrawLoop{
  DrawGraphic(GetX, GetY);
 }
 @Finalize
 {
  DeleteGraphic(imgBoss);
 }
 function AllroundShot(angle, color, way)
 {
  loop(way)
  {
   let x = GetX + offsetX(30, angle);
   let y = GetY + offsetY(30, angle);
   CreateShot01(x, y, 2, angle, color, 10);
   angle+=360/way;
  }
 }
}

このプログラムをマイクロスレッドを使って変えていってみましょう。

#東方弾幕風
#Title[マイクロスレッド]
#Text[マイクロスレッド]
#ScriptVersion[2]
script_enemy_main
{
 let imgBoss = "script\img\ExRumia.png";
 @Initialize{
  SetX(GetCenterX);
  SetY(GetClipMinY+120);
  SetLife(2000);
  SetTimer(60);
  LoadGraphic(imgBoss);
  SetTexture(imgBoss);
  SetGraphicRect(0, 0, 63, 63);
  Tmain;
 }
 @MainLoop{
  SetCollisionA(GetX, GetY, 24);
  SetCollisionB(GetX, GetY, 24);
  yield;
 }
 @DrawLoop{
  DrawGraphic(GetX, GetY);
 }
 @Finalize
 {
  DeleteGraphic(imgBoss);
 }
 task Tmain
 {
  yield;
  loop
  {
   loop(20){yield;}
   AllroundShot(GetAngleToPlayer, BLUE01, 16);
   loop(20){yield;}
   AllroundShot(rand(0, 360), RED01, 32);
  }
 }
 function AllroundShot(angle, color, way)
 {
  loop(way)
  {
   let x = GetX + offsetX(30, angle);
   let y = GetY + offsetY(30, angle);
   CreateShot01(x, y, 2, angle, color, 10);
   angle+=360/way;
  }
 }
}

変更されたスクリプトを見てみると、
@MainLoopの中の弾を発射する部分がなくなったのが分かると思います。
そして代わりにtask Tmainの中に移動しているのも分かります。
task Tmain{}の部分だけ見てみるとまるで関数のようです。
無限ループがありますが、breakもreturnもないので途中で処理を打ち切られたりもしなさそうです。
そのため、普通の関数なら@Initializeで起動された時点で無限ループに入り、
その先の処理が実行されない(≒弾幕風がフリーズ)ことになります。
しかし、実行してみると何の問題もなく動作します。
breakやreturnの代わりにある[yield]に何かありそうです。

マイクロスレッドは、関数の上位互換といってもいいでしょう。
定義、呼び出しも関数と同じように出来ます。
違う部分はというと「処理の中断が出来る」のです。
処理の中断に使うキーワードが[yield]です。
実際の処理の流れを見ていきましょう。

まずはじめに、@InitializeでTmainが起動されます。

Tmain;

起動されたら、起動直後に[yield]があるので、起動したところへ中断して戻ります。

task Tmain
{
 yield; @InitializeのTmainに戻る
 〜
}

戻ってきたので普通に@MainLoopに入り、敵の当たり座標を設定します。
すぐ後に[yield]があるので、Tmainの中断したところに行き、処理を再開します。

@MainLoop
{
 SetCollisionA(GetX, GetY, 24);当たり判定の設定
 SetCollisionB(GetX, GetY, 24);当たり判定の設定
 yield;@Tmainのyieldに行く
} task Tmain
{
 yield;A戻ってきて処理を再開
 loop
 {
  〜
 }
}

戻ってくるとすぐに無限ループに入ります。
無限ループに入ると、[loop(20){yield;}]があります。
yieldがあるのでまた@MainLoopのyieldに戻ります。
そして1フレーム目の処理が終了します。

@MainLoop
{
 yield;ATmainのyieldから
} task Tmain
{
 yield;
 loop
 {
  loop(20){yield;}@@MainLoopのyieldへ
  〜;
 }
}

2フレーム目も同様に、当たり判定の設定の後、yieldによりTmainの処理が再開されます。
処理の内容はloop(20){yield}なので、@MainLoopのyieldに戻ります。
これを20回繰り返すと、21フレーム目に次に進み、自機狙い全方位弾を1回撃ちます。

@MainLoop
{
 yield;@Tmainのyieldへ
} task Tmain
{
 yield;
 loop
 {
  loop(20){yield;}A@MainLoopのyieldから
  AllroundShot(GetAngleToPlayer, BLUE01, 16);B21フレーム目に実行される
  〜
 }
}

弾を撃ち終わると再びloop(20){yield}です。20フレームの間中断、復帰を繰り返し、
41フレーム目に、ランダム全方位弾を撃ちます。
その後は、無限ループの}が来るので、また最初のloop(20){yield}からやり直されることになります。

このようにして、弾の発射タイミングが制御されます。

メリット

マイクロスレッドを使うと、いくつかのメリットがあります。
まずはじめに、変数のスコープを短くすることが出来るのです。
たとえば、ランダム全方位弾を変数angleを使って、発射角度を5度ずつ増やして
撃ちたくなったとしましょう。

script_enemy_main{
 let angle = 0;
 〜
}
@MainLoop{
 〜
 if(count==40)
 {
  AllroundShot(angle, RED01, 32);
  count=0;
 }
 angle+=5;
 〜
}

こう書かれていたのが、


task Tmain
{
 let angle = 0;
 loop
 {
  〜
  AllroundShot(angle, RED01, 32);
  angle+=5;
 }
}

こう書く事が出来るようになるのです。
さらに、if文を使わなくてすむようになるので、
その分処理速度があがります。

マイクロスレッドを使うとプログラムが簡単に書けるようになる、 というのはこういうことなのです。

次回はもっと詳しく話をしていきましょう。


top
prev slash next
inserted by FC2 system