今回も前章の続きです。
前章では、「プレイヤー」「キーボード」の2つのモジュールを作りました。
キーボードは一つしかないので、モジュールの中に static変数 を使っても問題ありませんでしたが、
プレイヤーや雑魚、ボスといったモジュールはいくつも存在する可能性があります。
一つのモジュールで沢山のオブジェクトを処理するにはどうしたらよいでしょうか?
まず、少し本題からそれて、ある計算機を作ってみましょう。
「引数を二乗する関数を作りなさい」という課題があったらどう解決するでしょうか。
#include <stdio.h>
void Square(int v){
v = (v) * (v);
}
int main(){
int a = 5, b = 10;
Square( a );
Square( b );
printf("%d, %d\n", a, b);
return 0;
}
実行結果
5, 10
こうすれば、確かに引数を二乗しています。
しかし、引数に渡したa,bはSquare関数に渡した時点でコピーであって、main関数で持っている実体ではないので、
いくらSquareで変更を加えたところでmain関数内のa,bには影響がありません。
そこで、main関数が持っているa,bの値を二乗しようとすると・・
#include <stdio.h>
void Square(int *v){
*v = (*v) * (*v);
}
int main(){
int a = 5, b = 10;
Square( &a );
Square( &b );
printf("%d, %d\n", a, b);
return 0;
}
実行結果
25, 100
このようにアドレスを渡し、ポインタで受け取ることで実現可能ですね。
&a を渡し、*v で受け取れば *v は main関数 の a そのものを示しています。
もしポインタについての理解がまだアヤフヤであればこの辺で勉強して来て下さい。
改めてこのプログラムを見てみると、Square関数はmain関数が持っているaでもbでも関係なく処理出来ていました。
Square関数があるモジュールだとすると、このように処理をすることで、同じモジュールを使って複数のオブジェクトを計算出来そうです。
では、前章で紹介したプレイヤーモジュールについて、
モジュール内で持っていた変数を、main部で持ち、モジュールにはそのアドレスを渡すことで計算するように変更してみましょう。
/* Player.h */
#ifndef DEF_PLAYER_H //二重include防止 #define DEF_PLAYER_H typedef struct{ int Image; int y; } Player_t; // 初期化をする void Player_Initialize( Player_t *Player, int y ); // 動きを計算する void Player_Update( Player_t *Player ); // 描画する void Player_Draw( Player_t Player ); // 終了処理をする void Player_Finalize( Player_t Player ); #endif
赤字部が前章からの変更点です。
Image, y の変数は static 変数で持っていましたが、これはmain関数が保持するようにします。
引数で渡しやすいようにまとめて構造体にしました。
各関数の引数にはこの構造体を持たせます。Initialize, Update は値を変更する必要があるので、ポインタになっていますが、
Draw, Finalize は値を変更する必要が無いので、ポインタにはしていません。
また、2体プレイヤーを作って並べたいので Initialize に y の値を設定出来るように引数に追加しました。
/* Player.cpp */
#include "DxLib.h" #include "Keyboard.h" #include "Player.h" // 初期化をする void Player_Initialize( Player_t *Player, int y ){ Player->Image = LoadGraph("画像/キャラクタ01.png"); Player->y = y; } // 動きを計算する void Player_Update( Player_t *Player ){ if( Keyboard_Get( KEY_INPUT_UP ) > 0 ){ Player->y--; } if( Keyboard_Get( KEY_INPUT_DOWN ) > 0 ){ Player->y++; } } // 描画する void Player_Draw( Player_t Player ){ DrawGraph( 0, Player.y, Player.Image, TRUE ); } // 終了処理をする void Player_Finalize( Player_t Player ){ DeleteGraph( Player.Image ); }
Playerモジュールの中身です。
static変数を参照するのではなく、引数を参照するように変更しただけです。
/* main.cpp */
#include "DxLib.h" #include "Player.h" #include "Keyboard.h" int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){ ChangeWindowMode(TRUE),DxLib_Init(),SetDrawScreen( DX_SCREEN_BACK ); Player_t Player1, Player2; Player_Initialize( &Player1, 0 );// 初期化 Player_Initialize( &Player2, 200 );// 初期化 while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ){ Keyboard_Update(); Player_Update( &Player1 ); // 計算 Player_Update( &Player2 ); // 計算 Player_Draw( Player1 ); // 描画 Player_Draw( Player2 ); // 描画 } Player_Finalize( Player1 ); // 終了処理 Player_Finalize( Player2 ); // 終了処理 DxLib_End(); return 0; }
プレイヤーモジュールで計算する実体はmain部で定義しています。
プレイヤーモジュールにはそのアドレスを渡すことで、計算させています。
上で紹介した二乗を計算するモジュールを使った時と同じ使い方ですね。
どうでしょう、このようにすることで、同じモジュールを使って2つのオブジェクトを処理することが出来ました。
実行結果
プロジェクトをダウンロード
C言語では、このようにすることで効率的にモジュールが利用できることでしょう。
しかし変数と関数がセットで利用できないことにすごく不便を感じなかったでしょうか。
Playerモジュールの中に変数があった方が使いやすかったと思った人もいるでしょう。
色んなモジュールに使う変数をmain部が定義していちいち引数で渡してやるのは面倒だし、
変数が閉じていないので、安全性に問題があります。
「変数と関数をセットで使う」というやり方はC++で可能になります。
「クラス」と言えば聞いたことがある人もいるかもしれません。
クラスは大規模な設計をする上でとても便利な概念です。
大きなシステムやゲームの設計をする時はやはりC++が適しているので、
もっと大きな設計を学びたい人は是非C++を勉強してみて下さい。
※
結局他のファイルで定義した変数を参照する方法は紹介しませんでした。
紹介しませんでしたが、全く設計に問題無さそうでしたよね。
他のファイルで定義した変数を参照することなく設計出来ますから、
どのファイルからでも自由に変更できるグローバル変数を利用した設計はすべきではありません。
それを承知の上でどうしても知りたい人はこちらをご覧ください。
(C++でもシングルトンと呼ばれるCのグローバル変数のようなデザインパターンがあります。
適切に使えば良いのですが、初心者が使うと、悪い設計になりがちなので、最初はとにかくグローバル変数無しで設計してみて下さい)
- Remical Soft -