PlayLoud!!

Since1997

60fps調整など

とりあえず今回はシューティングじゃなくてアクションゲーム作りたいなぁと。
たくさんのキャラを走らせる実験。実行ファイルはこちら
走らせるのがメインじゃなくて、リフレッシュレートに関わらず60fpsを保つのが今回のプログラムの実験ポイント。
しかしうちの液晶はどれも60fpsにしか設定できなくて実験できなかった(苦笑)
ちなみに、前回のプログラム同様キャラは全部タスク化してあるのだが、FPSの測定とウェイト部分もタスク化してタスクリストに組み込んである。
グローバル変数宣言するより、構造体の中のワークエリアに変数を保存しておく方がいい感じするのだが。その辺りは好みの問題なのかな。
それよりも、そろそろソースが汚くなってきたので別ファイルにすることも考えないと。
ソースは以下。

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

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

// fps調整用
#define FLAME 60

// タスクの構造体
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;
}


// プレイヤーキャラに関する構造体と関数
struct MAN_WORK {
	float X, Y; // 座標
	int D; // 停止、移動状態
	int SP; // 速さ
	int gh[7]; // 画像ハンドル
};

// プレイヤーキャラのプロトタイプ宣言
void ManMove(TASK* task);

// プレイヤーの生成
void CreateMan(float x, float y,int s) {
	
	// タスクを生成する
	TASK* task=CreateTask(ManMove);
	if (!task) return;
	
	// ワークエリアへのポインタをキャストする
	assert(sizeof(MAN_WORK)<=WORK_SIZE);
	MAN_WORK* work=(MAN_WORK*)task->Work;

	// 画像を読み込み
	int man[7];
	LoadDivGraph( "running.bmp" , 7 , 7 , 1 , 24 , 32 , man );//画像を分割してimage配列に保存

	// ワークエリアに初期値を設定する
	work->X=x;
	work->Y=y;
	work->D=0;
	work->SP=s;
	for(int i=0;i<7;i++)
	{
		work->gh[i] = man[i];
	}
}

// プレイヤーの移動
void ManMove(TASK* task) {
	// ワークをキャスト
	MAN_WORK* work=(MAN_WORK*)task->Work;
	// 状態にあった画像を描画
	DrawGraph( (int)work->X , (int)work->Y , work->gh[(int)(work->D / 10)+3] , TRUE ) ;//画像を描画

	// 状態を変更
	work->D ++;
	work->D %= 30;

	//少しずつ移動
	work->X += work->SP;
	if(work->X>639)
	{
		work->X-=640;
		work->SP=GetRand(6)+1;
		work->Y=(float)GetRand(480);
	}

}

// fpsに関する構造体
struct FPS_WORK {
	int COUNT,T,AVE,COUNT0,F[FLAME]; // カウンタ、現在時間、ウェイト用基準時刻、平均値
};

// fpsのプロトタイプ宣言
void fps(TASK* task);

// fpsタスクの生成
void CreateFps() {
	
	// タスクを生成する
	TASK* task=CreateTask(fps);
	if (!task) return;
	
	// ワークエリアへのポインタをキャストする
	assert(sizeof(FPS_WORK)<=WORK_SIZE);
	FPS_WORK* work=(FPS_WORK*)task->Work;

	// ワークエリアに初期値を設定する
	work->COUNT=0;
	work->T=0;
	work->AVE=0;

}

// fps表示関数
void fps(TASK* task){
	// ワークをキャスト
	FPS_WORK* work=(FPS_WORK*)task->Work;

	int term; // 待ち時間

	// ウェイト処理
	if(work->COUNT==0) { // 60フレームの1回目なら
		if(work->T==0) // 完全に最初なら待たない
			term = 0;
		else //前回記録した時間をもとに計算
			term = work->COUNT0 + 1000 - GetNowCount();
	}
	else // 待つべき時間=現在あるべき時刻 - 現在の時刻
		term = (int)(work->COUNT0 + work->COUNT * (1000.0/FLAME)) - GetNowCount();

	if(term>0) // 待つべき時間だけ待つ
		Sleep(term);
	int gnt=GetNowCount();
	if(work->COUNT==0) //60フレームに1回基準を作る
		work->COUNT0=gnt;
	work->F[work->COUNT]=gnt-work->T;//1周した時間を記録
	work->T=gnt;

	//fpsの平均計算
	if(work->COUNT%FLAME==FLAME-1){
        work->AVE=0;
        for(int i=0;i<FLAME;i++)
            work->AVE+=work->F[i];
        work->AVE/=FLAME;
    }
    if(work->AVE!=0){
        DrawFormatString(0, 0,GetColor(255,255,255),"%.1fFPS",1000.0/(double)work->AVE);
        DrawFormatString(0,20,GetColor(255,255,255),"%dms"  ,work->AVE);
    }

	//カウンタを増やす
	work->COUNT = (++work->COUNT)%FLAME;
}


int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
        
    char Key[256];

    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理
	// 初期化フルスクリーン
	//if( DxLib_Init() == -1 ) return -1; 

	//タスクリストを初期化
	InitTaskList();	

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

	// fps表示タスクを生成
	CreateFps();
	// プレイヤータスクを生成
	for(int i=0;i<50;i++)
	{
		CreateMan((float)GetRand(640),(float)GetRand(480),GetRand(6)+1);
	}

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

		RunTask();
        ScreenFlip();
    }

    DxLib_End();
    return 0;
}