情報システム実験 K-13 組み込みオペレーティングシステム No.6
前回は,プログラムをいくつかの独立したモジュールに分ける方法を 説明しました.これらのモジュールbox, ball, racketのうち,main関数から定期的に 呼び出されて,処理を少しずつ進行させるもの(ballとracket)を,今後はタスク と呼ぶことにします.また,タスクが呼び出される時間間隔をティックと 呼ぶことにします.
今までに作った2つのタスク(ball, racket)は, 毎回おなじ処理をするだけでした. しかし,実際のゲームでは「ボールを落した」「ゲームオーバー」「一面クリアした」 などによって動きが変化します.
崩すブロックはまだ登場していませんが,まずは「ボールを落したら停止して, STARTボタンを押すと初期画面からやりなおし」という状態変化を付け加えてみましょう.
ゲームの状態は,以下の4つのうちのどれかであるとします.
ゲームの状態が今どうなっているのかを管理するgameタスクを新たに作りましょう. このタスクも独立したモジュールとして作り,そのインターフェース(game.h)は 以下のようにします.
1 enum state {START, RUNNING, DEAD, RESTART};
2
3 extern void game_step(void); // 1ティックの動作を行なう.
4 extern enum state game_get_state(void); // 今の状態を問い合わせる.
5 extern void game_set_state(enum state); // 状態を変更する.
1行目の宣言は, 新たな型である「enum state型」を宣言しています.この型の変数は 値としてSTART, RUNNING, DEAD, RESTARTのいずれかを取ります. (実際のプログラム実行中は整数の値で表され,STARTが0,RUNNINGが1, DEAD が2, ...となります.)
それぞれのタスクを呼び出すmain関数のループはgame_step()を加えて 以下のようになります.
while (1) {
ball_step();
racket_step();
game_step();
delay(INTERVAL);
}
ボールを扱うタスクball_step()の動作は,今の状態を意識して次のように 書きます.
...
#include "game.h"
...
void ball_step(void)
{
switch (game_get_state()) {
case START:
ボールの位置,速度を初期状態にし,ボールを表示する.
break;
case RUNNING:
ボールのアニメーションを1ステップ行なう.
if (ボールが落ちた)
game_set_state(DEAD);
break;
case DEAD:
何もしない.
break;
case RESTART:
現在のボールを画面から消し,
ボールの位置,速度を初期状態にし,ボールを表示する.
break;
}
}
racketモジュールも同じようにゲームの状態で場合分けして処理するように書き換えます. また,gameモジュールは以下のように書けます.
#include "game.h"
static enum state current_state; // 現在の状態
enum state game_get_state(void) { return current_state; }
void game_set_state(enum state new_state) {
current_state = new_state;
}
void game_step(void)
{
switch (game_get_state()) {
case START:
if (STARTキーが押された)
game_set_state(RUNNING);
break;
...
case RESTART:
/* 次のティックはRUNNING状態にする.*/
game_set_state(RUNNING);
break;
}
}
いよいよ残った部分,ブロックを扱うblockタスクを作っていきましょう. 例によって,block.h と block.c を作ります.block.h のインターフェースは 以下のようになります.
extern void block_step(void);
最初は,簡単のためにブロックを一つだけ表示し, そのブロックにボールが当たったらクリア(ゲーム終了) というバージョンを作ってみましょう.
ゲームの状態は1個増えて下図のようになります. 新たな状態であるCLEAR状態では,DEAD状態と同様に アニメーションを停止してSTARTキーが押されるのを待ちます. STARTキーが押されると,START状態に移るようにします.
game.h のenum stateの定義にCLEARを加え, また各々のタスクのswitch文でCLEAR状態を扱う選択肢を加えましょう. gameタスクでは,CLEAR状態でSTARTキーが押されるとSTART状態に移るようにします. その他のタスクでは,DEAD状態と同様に,単に何もしないようにすれば良いでしょう. main関数にも忘れずにblock_step()の呼び出しを加えます.
while (1) {
ball_step();
racket_step();
block_step();
game_step();
delay(INTERVAL);
}
blockタスクの本体である,block.c の中の block_step() 関数を作りましょう. これも,他のタスクと同様に,今のゲーム状態によってswitch文で分岐するようにします.
#include "gba.h"
#include "ball.h"
#include "game.h"
#include "box.h"
#include "block.h"
static な変数の定義
void block_step(void)
{
switch (game_get_state()) {
case START:
ブロックを表示する.
break;
case RUNNING:
if (ボールがブロックとぶつかっている)
game_set_state(CLEAR);
break;
...
}
}
ブロックはboxで表し,ボールとぶつかっているかどうかの判定は racketの時と同様にcross()を使うと簡単でしょう.