キーボード入力を受け取る
そろそろ自機を左上隅から移動させてやります。
中身の説明をする前に移動できるようにしたコード全文を掲載しますね。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <assert.h> #include "SDL.h" //============================================================================= // // マクロ // //============================================================================= // // エラー/デバッグ用出力。出力後すぐにフラッシュされる。 // DEBUG_OUT(("format %d\n", num)) // のように括弧を2重にする必要がある。 // #define ERROR_OUT(args) do { printf args ; fflush(stdout); } while(false) #if defined(DEBUG) || defined(_DEBUG) #define DEBUG_OUT(args) do { printf("Dbg: "); printf args ; fflush(stdout); } while(false) #else #define DEBUG_OUT(args) #endif #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 #define DEFAULT_COLOR_KEY 0x00ff00ff // カラーキーとして指定する色。 //============================================================================= // // 型定義、構造体、列挙体 // //============================================================================= // 画像リソースの識別番号。 // "ElementCount"を最後に定義しておくと、全部で幾つの画像リソースがあるのかが分かる。 enum ImageResource { ImageResource_Fighter, // 自機の画像 ImageResource_ElementCount, }; //============================================================================= // // グローバル変数 // //============================================================================= // このサーフェイスの中身が画面に対応するものと思ってください。 // あちこちから参照するのでためらわずグローバル。 static SDL_Surface* screen; // ゲーム中で使用する画像リソース。 static SDL_Surface *(imageResources[ImageResource_ElementCount]); // 自機の位置。 static int fighterX, fighterY; //============================================================================= // // プロトタイプ宣言 基本的に順番入れ替えで対応しますがどうしてもって奴ら。 // //============================================================================= static void closeGame(int exitCode); // // 画像リソースを読み込む。 // static void loadImageResource(ImageResource id) { // 最も素朴な方法で。 switch(id) { case ImageResource_Fighter: imageResources[id] = SDL_LoadBMP("Resources/Images/Fighter.bmp"); break; default: assert(false); break; } // 読み込めたかチェック。 if(imageResources[id] == NULL) { fprintf(stderr, "画像リソースを読み込めません。 id=%d :%s\n", id, SDL_GetError()); closeGame(1); } // カラーキーに設定された色は透過される。 SDL_SetColorKey(imageResources[id], SDL_SRCCOLORKEY | SDL_RLEACCEL, DEFAULT_COLOR_KEY); } // // 全てのリソースを解放する。 // static void releaseAllResources() { for(int i = 0; i < ImageResource_ElementCount; ++i) { if(imageResources[i] != NULL) { SDL_FreeSurface(imageResources[i]); imageResources[i] = NULL; // 間違えて使わないようにNULLを入れておく。 } } } // // グラフィックス機能の終了処理を行う。 // 今のところ中身はない。openがあるのにcloseが無いのは気持ちが悪かったので作っただけ。 // static void closeGraphics() { } // // ゲームの終了処理を行う。 // 内部でexit()を呼ぶためここでアプリケーションは終了する。 // exitCode: exit()に渡す終了コード。 // static void closeGame(int exitCode) { // 全てのリソースを解放。 releaseAllResources(); // 描画機能を閉じる。 closeGraphics(); // SDL終了。 SDL_Quit(); // しゅうりょー exit(exitCode); } // // グラフィックス機能の初期化を行う。 // 戻り値: 成功したらtrue、失敗したらfalse。 // static bool openGraphics() { // 今のところ中身はこれだけ screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_SWSURFACE); if (screen == NULL) { fprintf(stderr, "%dx%dの画面モードに設定できませんでした。: %s\n", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_GetError()); return false; } return true; } // // ゲームの初期化を行う。 // 戻り値: 成功したらtrue、失敗したらfalse。 // static bool openGame() { // 標準出力と標準エラー出力をテキストファイルに設定。 // こうしておくとコマンドラインから起動しなくても出力内容を見れる。 freopen("stdout.txt", "w", stdout); freopen("stderr.txt", "w", stderr); // SDL_Initではグラフィックス以外にもタイマーや音声機能の初期化も行うことができる。 if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { fprintf(stderr, "SDLの初期化に失敗しました: %s\n", SDL_GetError()); return false; } // グラフィックス機能の初期化。 if(!openGraphics()) { return false; } return true; } // // ゲームの状態を1フレーム分先へ進める。 // static void run() { if(SDL_GetKeyState(NULL)[SDLK_LEFT]) { fighterX -= 2; } if(SDL_GetKeyState(NULL)[SDLK_RIGHT]) { fighterX += 2; } if(SDL_GetKeyState(NULL)[SDLK_UP]) { fighterY -= 2; } if(SDL_GetKeyState(NULL)[SDLK_DOWN]) { fighterY += 2; } } // // 現在の状態を描画する。 // static void draw() { // 画像を(100, 100)へ転送。 SDL_Rect destination; destination.x = fighterX; destination.y = fighterY; SDL_BlitSurface(imageResources[ImageResource_Fighter], NULL, screen, &destination); // 画面を更新。めんどくさいので画面全体。 SDL_UpdateRect(screen, 0, 0, screen->w, screen->h); } // // 60fpsに固定できるように適切な時間待機する。 // 戻り値: 最近1秒間の平均FPS。 // static float waitFps() { // staticなローカル変数は関数を抜けてからも値が保持されることに注意。 static const int msPerFrame = (int)(1000.0 / 60 + 1); // 1000msを60分して四捨五入。1フレームあたりにかけられる時間。 static int last = 0; // 前回のwaitFps終了時間。 int now = SDL_GetTicks(); // SDL初期化後からの経過時間(ms単位)。 int difference = now - last; if(difference < msPerFrame) { // 1フレームあたりにかけられる時間より早く動作と描画が終わったとき SDL_Delay(last + msPerFrame - now); last += msPerFrame; } else { // 処理落ちが発生したとき last = now; } // 1秒間の平均FPSも算出する。 // static int lastAveraged = 0; // 前回、1秒間の平均FPSを算出した時間。 static int callCount = 0; // 前回、1秒間の平均FPSを算出してからwaitFps()が呼ばれた回数。 static float averageFps = 60; // 最近1秒間の平均FPS。 ++callCount; if(lastAveraged + 1000 < now) { averageFps = (float)callCount / (now - lastAveraged) * 1000; lastAveraged = now; callCount = 0; } return averageFps; } int main(int argc, char *argv[]) { // ゲームの初期化。 if(!openGame()) { return 1; } // 自機画像の読み込み loadImageResource(ImageResource_Fighter); // メインループ int done; done = 0; while(!done) { // イベントの確認。詳細はSDLのドキュメントを見る。 SDL_Event event; while ( SDL_PollEvent(&event) ) { switch (event.type) { case SDL_MOUSEMOTION: break; case SDL_MOUSEBUTTONDOWN: break; case SDL_KEYDOWN: /* Any keypress quits the app... */ break; case SDL_QUIT: done = 1; break; default: break; } } // 1フレームごとの動作 run(); // 描画 draw(); // 60fpsに保つために待機 DEBUG_OUT(("fps=%f\n", waitFps())); } closeGame(0); return 0; }
実行してみると、
あ・ぁ
通った跡が残っとる
これは後で直すとして変更したところを解説。
// 自機の位置。 static int fighterX, fighterY;
これは見たまんま。
// // ゲームの状態を1フレーム分先へ進める。 // static void run() { // Escキーを押したときはプログラムを終了。 if(SDL_GetKeyState(NULL)[SDLK_ESCAPE]) { closeGame(0); } // 自機の移動 if(SDL_GetKeyState(NULL)[SDLK_LEFT]) { fighterX -= 2; } if(SDL_GetKeyState(NULL)[SDLK_RIGHT]) { fighterX += 2; } if(SDL_GetKeyState(NULL)[SDLK_UP]) { fighterY -= 2; } if(SDL_GetKeyState(NULL)[SDLK_DOWN]) { fighterY += 2; } }
キーボード入力の受け取り方を見てください。
SDL_GetKeyState(NULL)で現在のキーボードの状態を格納した配列が返されます。
そこの"SDLK_"で始まるキーのインデックスを指定してやると、状態を取得できるって訳です。
ちなみに値が1のとき押されている状態で、0の時離されている状態です。
最初にEscキーの入力を確認し押されていたら終了するようにしました。
あとに書いていますがこれやらないとプログラムが終了できなくなります。
自機の移動の方も、今の状態だと斜め移動したときに速さが√2倍になってしまいますが直すのめんどくさいので放置。
これはこれでゲームの1要素ってことで(ぉ
気になる人は個人的に修正してください。
// // 現在の状態を描画する。 // static void draw() { // 画像を(100, 100)へ転送。 SDL_Rect destination; destination.x = fighterX; destination.y = fighterY; SDL_BlitSurface(imageResources[ImageResource_Fighter], NULL, screen, &destination); // 画面を更新。めんどくさいので画面全体。 SDL_UpdateRect(screen, 0, 0, screen->w, screen->h); }
コメント書き直すの忘れてたwww
大事なのはdestinationのxとyにfighterX, Yを入れてるってことね。
それ以上でもそれ以下でもないです。
そして気づきにくいもう一点。
switch (event.type) { case SDL_MOUSEMOTION: break; case SDL_MOUSEBUTTONDOWN: break; case SDL_KEYDOWN: /* Any keypress quits the app... */ break; case SDL_QUIT: done = 1; break; default: break; } }
よく見てください。
SDL_KEYDOWNのところ、breakしてなかったのですがbreakを追加しました。
ここにbreakがないままだとどうなっているかというと、何かキーを押すとSDL_QUITのところへ流れ落ちてそのまま実行が終了してしまうわけです。
さて通った跡が残ってしまう問題もここで解決しておきましょう。
そもそもどうして跡が残ってしまうかというと、画面のクリアをしていないからなのです。
前に自機を描いたキャンバス上に位置をずらしてまた描けばそりゃ跡が残る。
そういうわけで画面のクリアをしましょう。
// // 現在の状態を描画する。 // static void draw() { //画面全体を黒でクリア SDL_FillRect(screen, NULL, 0x00000000); SDL_Rect destination; destination.x = fighterX; destination.y = fighterY; SDL_BlitSurface(imageResources[ImageResource_Fighter], NULL, screen, &destination); // 画面を更新。めんどくさいので画面全体。 SDL_UpdateRect(screen, 0, 0, screen->w, screen->h); }