PlayLoud!!

Since1997

タスクシステム

書籍「シューティングゲームプログラミング」を参考にタスクシステムを作ってみた。

シューティングゲーム プログラミング

シューティングゲーム プログラミング

動画はわかりにくいが、画面内を動いている円のひとつが1タスク。
ついでにスペースキーで新しいタスク(円)を追加できる。

それぞれのタスクは関数ポインタで動きの処理を指定してあるので、キャラクタのバリエーションを増やしたいときは関数を増やせばいいだけ。
あとはマップデータに合わせてタスクを生成する処理を繰り返せば独立して動くキャラクタがいくらでも作れるというわけだ。
ついでに、自機もスクロールもBGMもタスク化すれば、必要な処理を同じように追加するだけでゲームが作れそうだ。
実行ファイルはこちら
ソースは以下。(※「シューティングゲームプログラミング」のソースをかなり参考にしてあります)

// タスクシステム実験

#include <stdio.h>
#include <assert.h>
#include "DxLib.h"

//==============================================================

// ワークエリアのサイズ、タスク数
#define WORK_SIZE 256
#define NUM_TASKS 1024

// タスクの構造体
struct TASK {

	// 処理関数へのポインタ
	void (*Func)(TASK* task);

	// 前後のタスクへのポインタ
	TASK* Prev;
	TASK* Next;

	// ワークエリア
	char Work[WORK_SIZE];
};

// 処理関数の型宣言
typedef void (*FUNC)(TASK* task);

// タスクの連結リスト
TASK* ActiveTask;
TASK* FreeTask;


//==============================================================

// タスクリストの初期化
void InitTaskList() {
	
	// タスク用メモリの確保
	TASK* task=new TASK[NUM_TASKS+2];

	// アクティブタスクリストの初期化
	ActiveTask=&task[0];
	ActiveTask->Prev=ActiveTask->Next=ActiveTask;

	// フリータスクリストの初期化
	FreeTask=&task[1];
	for (int i=1; i<NUM_TASKS+1; i++)
		task[i].Next=&task[i+1];
	task[NUM_TASKS+1].Next=FreeTask;
}

// タスクの実行
void RunTask() {
	for (TASK *task=ActiveTask->Next, *next; 
		next=task->Next, task!=ActiveTask; task=next)
		(*task->Func)(task);
}

// タスクの生成
TASK* CreateTask(FUNC func) {

	// フリータスクリストが空ならば生成を中止する
	if (FreeTask->Next==FreeTask) return NULL;

	// フリータスクを1個取り出す
	TASK* task=FreeTask->Next;
	FreeTask->Next=task->Next;
	
	// 処理関数と前後タスクへのポインタを設定する
	task->Func=func;
	task->Prev=ActiveTask->Prev;
	task->Next=ActiveTask;

	// 前後タスクのポインタを変更する
	task->Prev->Next=task;
	task->Next->Prev=task;
	
	// 生成したタスクを返す
	return task;
}

// タスクの削除
void DeleteTask(TASK* task) {
	
	// アクティブタスクリストからタスクを削除する
	task->Prev->Next=task->Next;
	task->Next->Prev=task->Prev;
	
	// 削除したタスクをフリータスクリストに挿入する
	task->Next=FreeTask->Next;
	FreeTask->Next=task;
}


//==============================================================

// 敵処理関数のプロトタイプ宣言
void EnemyAppear(TASK* task);
void EnemyAttack(TASK* task);
void EnemyExplode(TASK* task);

// 敵のワークエリア構造体
struct ENEMY_WORK {
	
	// 座標と速度
	float X, Y, VX, VY;

	// 耐久力とタイマー、画像ハンドル
	int Vit, Timer, gh;
};

// 敵の処理関数(出現)
void EnemyAppear(TASK* task) {
	//puts("EnemyAppear");
	ENEMY_WORK* work=(ENEMY_WORK*)task->Work;
	DrawGraph( (int)work->X , (int)work->Y , work->gh , TRUE ) ;//画像を描画

	// 出現の処理を行う(詳細は省略)
	
	// 一定時間が経過したら攻撃を開始する
	work->Timer++;
	if (work->Timer==6) task->Func=EnemyAttack;
}

// 敵の処理関数(移動)
void EnemyAttack(TASK* task) {
	((ENEMY_WORK*)task->Work)->Vit--;

	// 移動の処理を行う
	ENEMY_WORK* work=(ENEMY_WORK*)task->Work;
	work->X += work->VX;
	work->Y += work->VY;

	// 画面の隅で跳ね返る
	if (work->X<0)
	{
		work->X = 0;
		work->VX = -work->VX;
		work->Timer++;
	}
	if (work->Y<0)
	{
		work->Y = 0;
		work->VY = -work->VY;
		work->Timer++;
	}
	if (work->X>639)
	{
		work->X = 639;
		work->VX = -work->VX;
		work->Timer++;
	}
	if (work->Y>479)
	{
		work->Y = 479;
		work->VY = -work->VY;
		work->Timer++;
	}

	if (work->Timer>=20)
	{
		task->Func=EnemyExplode;
	}

	DrawGraph( (int)work->X , (int)work->Y , work->gh , TRUE ) ;//画像を描画

}

// 敵の処理関数(消滅)
void EnemyExplode(TASK* task) {
	
	// 消滅の処理を行う(詳細は省略)
	
	// 一定時間が経過したらタスクを削除する
	ENEMY_WORK* work=(ENEMY_WORK*)task->Work;
	work->Timer++;
	if (work->Timer==12) DeleteTask(task);
}

// 敵の生成
void CreateEnemy(float x, float y) {
	
	// タスクを生成する
	TASK* task=CreateTask(EnemyAppear);
	if (!task) return;
	//画像ハンドルをワークエリアにセット
	int GrHandle ;
	GrHandle = LoadGraph( "circle.bmp" ) ;
	
	// ワークエリアへのポインタをキャストする
	assert(sizeof(ENEMY_WORK)<=WORK_SIZE);
	ENEMY_WORK* work=(ENEMY_WORK*)task->Work;

	// ワークエリアに初期値を設定する
	work->X=x;
	work->Y=y;
	work->VX=5;
	work->VY=5;
	work->Vit=5;
	work->Timer=0;
	work->gh=GrHandle;
}

//==============================================================

// テスト用のメインルーチン:
// 自機と敵を生成して動かす。

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
        
    char Key[256];
	bool key_flag=true; //スペースキー解放フラグ
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理

    SetDrawScreen( DX_SCREEN_BACK ) ;                                                 //描画先を裏画面に設定

	InitTaskList();
//	CreateMyShip(0, 0);
	//最初の敵を設置
	for(int i=80;i<640;i+=80)
	{
		for(int j=60;j<480;j+=60)
		{
			CreateEnemy((float)i, (float)j);
		}
	}

	//ループ
    while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){
           //↑メッセージ処理        ↑画面をクリア         ↑キーボード入力状態取得       ↑ESCが押されると終了

		if(CheckHitKey(KEY_INPUT_SPACE) && key_flag) //スペースキーで新しい敵をセット
		{
			CreateEnemy(320,240);
			key_flag=false;
		}
		if(!CheckHitKey(KEY_INPUT_SPACE)) //スペースキーを放したかどうかのチェック
		{
			key_flag=true;
		}
		//タスク一周
		RunTask();

		//fps();
		//count++;
        ScreenFlip();
    }

    DxLib_End();
    return 0;
}