2008/8/19 更新
ココでは基本的なC言語によるゲームの作り方を、とにかく「わかりやすい」をモットーに紹介しています。
C言語歴1ヶ月?十分です。 ココで勉強すれば、例えば↓以下の写真のようなゲームが作れます!
(下のリンクからゲームがダウンロード出来ます)
パズルゲーム (ぷよぷ○風) |
四聖龍神録 (東方風STG) (ゲームDL公開中!) |
アクション (ボンバーマ○風) |
前書き
9/4 サンプルで使う画像を変更したので新しくダウンロードしてください
ゲーム作成は難しいと思っているあなた、間違いです!!
基本的なC言語で誰でも簡単に本格的な2Dゲームプログラミングがとても簡単にできます!
if文とfor文、簡単な関数扱いしか知らない人でも簡単に
初代FFやドラクエ、2Dシューティングのようなゲームくらい作れるようになるでしょう。
ココでは「誰にもわかる」をモットーにとにかくわかりやすく書いています。
例えば↓以下の写真のようなゲームが作れます!(管理人作)
ホラーアクション(○魔界村風) |
パズルゲーム(ぷよぷ○風) |
アクション(ボンバーマ○風) |
RPG(とくに何も無い) |
〜入門編〜(基本的な関数の使い方)
0. (導入)DXライブラリを手に入れる。(全てはココから)
導入・・それは新しいことを始めることであり、人はみな敬遠してしまいます。みんなここでつまずいてしまいます!!
しかし、本当に簡単なんです。全然難しくないんです。難しいと思うのはみんなの先入観なんです!!
是非3章までは面倒でも読んで実行してください!!必ず、理解できますから!
まず、2Dゲームの作成はDXライブラリを使用します。
C言語単体では、描画するための関数が無いため、ライブラリを利用する必要があります。
「超簡単」ですから是非試してみてください!
こちらの「DXライブラリのダウンロード」からDXライブラリをダウンロードしてください。
DXライブラリを使ってプログラムを書くので、全てはここから始まります。
このようなサイトに飛びます。VisualC++をお持ちの方は上を、BorlandC++をお持ちの方は下をクリックしてダウンロードしてください。
ここでは両方のコンパイラの説明するとややこしくなるので、VC++の説明のみ行います。
Borlandのコンパイラをどうしても使いたくて、設定方法がわからなければ本家DXライブラリのHPをお読み下さい。
どちらのコンパイラも持っていない人または、無料版のコンパイラをお持ちの方は、こちらをご覧下さい。
VisualC++(VC++)というのは
こんなロゴのソフトの事です。
では、VisualC++用のDXライブラリをデスクトップにダウンロードしたとしましょう。
Borlandをお使いの方は、こちらの導入説明をお読みください。
http://homepage2.nifty.com/natupaji/DxLib/dxuse_bcc.html
Visual Studioをお使いの方は引き続きお読みください。
デスクトップに
このような実行ファイルが表示されるはずです。これをダブルクリックしてみます。
するとこんな感じで解凍(展開)が促されます。
デスクトップに展開してみましょう。
展開したら、こんなフォルダが生成されます。
このフォルダを開きます。するとこのように6つのフォルダ・ファイルが表示されます。
その中の「サンプルプログラム実行用フォルダ」を開いてください。その中にある
DxLib.sln を開きます。すると、VC++が起動します。
ここで2005を使っている人は「変換しますか?」のようなウィンドウが出ますので、指示に従って変換してください。
VC++が起動しました。これでもう、コンパイルできます。キーボードの上にある「F5」を押して実行してください。
ここで「はい」を選択します。
全画面でこんな画面が出たら成功です。
ここでうまく行かなかった人、DXライブラリの設定がうまくいっていない可能性が高いです。
無料版のコンパイラをお持ちの場合は、設定が必要ですので、こちらでDXライブラリの設定を確認してください。
では、これがどういうソースプログラムでできているか、確認します。
先ほどの画面の矢印部のアイコンをクリックしてください。
これによりソースファイルを確認する画面である、「ソリューションエクスプローラー」が表示されます。
上のようなアイコンが表示されていない場合は、
メニューバーの「表示」からでもソリューションエクスプローラーが表示できます。
ソリューションエクスプローラーが表示されました。ここで、矢印の「+」をクリックしてみます。
さらに、「SourceFiles」の+もクリックします。そしてあらわれた「test.cpp」をダブルクリックします。
すると左側にソースファイルが表示されます。
このソースファイルをコンパイルした結果が先ほどの実行結果だったわけです。
では、一つ練習してみます。
このソースファイルを全選択→Deleteでソースを全部消します。
全選択してDelete
ソースが全部消えました。
そして、以下のプログラムソースをコピーして貼り付けてください。
(棒線を含めてはいけません。うまくプログラムコードだけをコピーしてください。)
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ;//ウィンドウモードに変更する関数 if( DxLib_Init() == -1 ) return -1;//初期化処理する関数 LoadGraphScreen( 0 , 0 , "test1.bmp" , TRUE ) ;//画像を表示する関数 WaitKey() ;//何か入力があるまで待つ関数 DxLib_End() ;//終了処理をする関数 return 0 ; }
つまりこのように範囲選択してコピーします。
↑これ画像ですからお間違いなく。
VC++のソース画面に貼り付けます。
ソースコードを貼り付けるとこうなります。この状態で、また「F5」を押してコンパイル 実行してみます。
ここで「はい」を押します。
実行画面に今度はウィンドウで表示されたら成功です。
実は、これは最初のソースファイルに、「ウィンドウ画面で表示する関数」を付け足したためにこうなりました。
このように、このサイトでは、各章のいたるところにある、先ほどのような
棒線にはさまれた「サンプルプログラム」をコピー、貼り付けして、実行し、内容を確認して行ってください。
後々、DXライブラリに登場する関数の内容が知りたくなったら、本家DXライブラリのサイトで確認するとよいでしょう。
(導入時に見ると混乱するので、まだ関数リファレンスページは見ないほうがいいと思います。後々必要になったら
確認してください) 本家DXライブラリの関数リファレンスページ
DXライブラリ著作権表示
DX Library Copyright (C) 2001-2006 Takumi Yamada.
http://homepage2.nifty.com/natupaji/DxLib/
1. (導入)必要な画像を保存する。(必須)
注意!!1枚画像を保存してください。(必ず)
当サイトのプログラム解説にはほぼ毎回、以下の画像ファイルを使用します。
先ほどの「サンプルプログラム実行用フォルダ」
のフォルダの中に以下の画像を保存してください。
↓保存する画像↓ char.png |
上記画像にオンマウスして「右クリック」>「名前を付けて画像を保存」を選択してください。
ちゃんと先ほどのデスクトップにある「DxLib_VC」フォルダの中の「サンプルプログラム実行用フォルダ」に入れてくださいね。
(Borlandの人は「Debug」フォルダの中に保存します。)
名前が「char.png」になっている事を確認してください。
右クリック>保存を行う時は
保存する時の画面がこのようになっている事を確認してください。「サンプルプログラム実行用フォルダ」ですよ!
(Borlandの人はDebugフォルダの中)
これで準備において全ての作業が完了です!!
以下の章のサンプルプログラムを、お使いのコンパイラにコピーペーストして結果を確認していってください。
ここまででわからないことがあれば、何でも掲示板で聞いてください。
2. ウィンドウを表示させる。
まずはゲームウィンドウを表示しましょう。DXライブラリの最初の最初のプログラムです。
以下のサンプルを実行することで、フルスクリーンでウィンドウが表示できます。
0章、1章をお読みの方は結構ですが、
先ほどダウンロードしたフォルダの中の「サンプルプログラム実行用フォルダ」のフォルダの中の
VisualStdioでしたらDxLib.slnを開き、プログラムが書ける状態にしてください。
コピーペーストで、そこに以下のサンプルを書き、実行してください。
サンプルプログラムは何かキーを押すと終了します。
プログラムの意味はわからなくていいです。とにかく一度実行してみてください。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( DxLib_Init() == -1 ) return -1; WaitKey() ; DxLib_End() ; return 0 ; }
実行結果
今回はウィンドウを表示するだけですので、画面は真っ黒のままです。
見たことの無い文字が色々書いてあるので戸惑うかもしれませんが、このプログラムは
毎回必ず書く最小の文章で、全く覚える必要なし!珍しいややこしい記述に惑わされないで下さい。
意味不明なint WINAPI....なんて文章は理解する必要ありません!
先ほどのDXライブラリの最小プログラムを見て見ましょう。
#include "DxLib.h"
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR
lpCmdLine, int nCmdShow ){
if( DxLib_Init() == -1 ) return -1;
ココ
WaitKey() ;
DxLib_End() ;
return 0 ;
}
毎回この部分はコピーペーストして使うんです。「ココ」に実質的な処理を書いていきます。理解する必要はありません。
毎回コピーペストすればいい文章です。
プログラムの各行の意味を知りたい人、以下を読んでください。
今までプログラムを書くときメイン関数をかいていましたね。それにあたるのが
int WINAPI WinMain(){........です。この関数の中に処理を書いていきましょう。
WaitKey() ; 以外の部分はいつも必ずかかなければならない部分です。
では、上から順に見ていきましょう。
#include "DxLib.h" これはDXライブラリを使用しますよという意味。いつも書いている#include<stdio.h>はいりません。
今回新しく出てきた関数は3種類。覚える必要はありません。
DxLib_Init() DXライブラリを初期化する関数(-1が返ってきたらエラーを意味する)
WaitKey() 何か入力キーがあるまで処理をとめる関数
DxLib_End() DXライブラリを終了する関数。
これらは毎回書く必要があるのでいつもコピーしましょう。
ゲームを作成している段階から毎回フルスクリーン起動モードでは、デバックが出来ないので
ウィンドウモードにしてみましょう。ウィンドウモードに切り替える関数は
ChangeWindowMode( TRUE ) ;
を使用します。この関数を最初にはさめばウィンドウ化します。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 WaitKey() ; // キーの入力待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
これらは毎回書く必要がある文章ですからサンプルをコピーペーストしていきましょう。
ここに処理を書いていくという部分にこれから処理を書いていきます。
何か判らない事、トラブル等ありましたら、気軽に掲示板で質問してください。
3. 画像を表示する。
新しく出てきた関数を赤で示してあります。
1章でダウンロードした「サンプルプログラム実行用フォルダ」内に入っているchar.pngを表示してみましょう。
char.pngの画像はこちらですね。すでに1章で保存はされたと思います。
char.png
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 LoadGraphScreen( 0 , 0 , "char.png" , TRUE ) ; WaitKey() ; // 結果を見るためにキー待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
ちゃんと表示できなかった人はこちらで、問題を解消してください。
画像をロードして表示する関数はLoadGraphScreen関数を使います。
このような引数の順番で使用します。ファイルの場所に関しては、
今のフォルダより上のフォルダにあるtest.bmpを指定したい時は "../test.bmp"と
今のフォルダより下のimageフォルダにあるtest.bmpを指定したい時は "image/test.bmp"と書いてください。
TRUEについてはまた後で説明します。
xは左が0,yは上が0です。今回(0,0)で指定しているので一番左上に表示されているわけですね。
何か判らない事、トラブル等ありましたら、気軽に掲示板で質問してください。
4. 透過処理をした画像を表示する。
透過処理とはどのような処理か?まず、先ほどの画像に背景を付けて表示してみましょう。
背景に使うのはback.bmpです。画像の中身はこちらです。
back.bmp (を1/4に縮小したもの)
これを単純に重ねて表示するとこうなります。
実行結果
では、透過処理をしてみます。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 LoadGraphScreen( 0 , 0 , "back.bmp" , FALSE ) ; LoadGraphScreen( 0 , 0 , "char.png" , TRUE ) ; WaitKey() ; // 結果を見るためにキー待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
見ていただけたら判る通り、透過処理をするとキャラの周りがうまく背景になっていますね。
先ほどの関数の第四引数は透過処理をするかどうかのフラグなんです。
LoadGraphScreen( 0 , 0 , "back.bmp" , FALSE ) ;
TRUE=透過処理をする。 FALSE=透過処理をしない。
背景は透過処理する必要が無いのでFALSEで、キャラはTRUEにしましょう。
--発展知識--
透過処理されるのは、限りなく黒に近い色か、画像に予め透過処理をしてある部分です。
画像は光の3原色で表します。bmpファイルではRGBそれぞれ0〜255の階調で表しますが、この値を
(R,G,B)=(0,0,0)の色つまり真っ黒にすると透過されます。黒だけど、透過させたくないという部分がありましたら
その部分に見えない程度の色をつけましょう。(例(R,G,B)=(10,10,10))
JPEGにすると真っ黒のピクセルが真っ黒ではなくなるので透過されません。JPEGは使わないようにしましょう。
容量が多くてかまわない場合はbmpで、容量を軽くしたい場合はpng形式の画像にしましょう。
------------
5. 色を指定して線・円・四角形を表示する。
色は光の3原色(R,G,B)=(赤、緑、青)の混合で指定します。
光は全ての色を混合させると白になります。輝度は0〜255で指定します。
ですから白の場合は 白=(255,255,255) 反対に何も光がなければ黒になります。黒=(0,0,0)
赤をしていしたければ 赤=(255,0,0)となります。
色を取得する関数はGetColor();という関数を使用します。この関数の引数に3色の値を
指定します。返り値はその色のハンドルがint型で渡されるため、任意の変数に代入してください。
例
int Color_Red;
Color_Red = GetColor( 255 , 0 , 0 );
こうする事で変数の中に赤を取得した事になります。
〜線〜
線はDrawLine関数を使用します。
座標(x1,y1)から(x2,y2)へラインを引く時
DrawLine ( x1 , y1 , x2 , y2 , Color_Flag);
で指定します。
第五引数のColor_Flagはint型の先ほど格納した色のハンドルを指定します。
〜四角形〜
四角形はDrawBox関数を使用します。
四角形の左上の座標(x1,y1)から右下の座標(x2,y2)の四角形を書くとき
DrawBox ( x1 , y1 , x2 , y2 , Color_Flag , Fill_Flag );
で指定します。
Color_Flagは同上。
Fill_Flagは四角形の中を塗りつぶすかどうかのフラグで
FLASE=塗りつぶさない
TRUE=塗りつぶす
という意味です。
〜円〜
円はDrawCircle関数を使用します。
中心の座標(x,y)、半径rの円をを書くとき
DrawCircle ( x , y , r , Color_Flag , Fill_Flag );
で指定します。
Color_Flag、Fill_Flagは同上
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int White,Red,G_Blue; White = GetColor( 255 , 255 , 255 ) ; // 白色の値を取得 Red = GetColor( 255 , 0 , 0 ) ; // 赤色の値を取得 G_Blue = GetColor( 0 , 255 , 255 ) ; // 緑と青の混合色の値を取得 DrawLine ( 10 , 10 , 630 , 10 , White ) ; // 線を描画 DrawBox ( 10 , 50 , 310 , 100 , G_Blue , FALSE) ; // 四角形を描画 DrawBox ( 10 , 150 , 310 , 200 , G_Blue , TRUE ) ; // 四角形を描画(塗りつぶし) DrawCircle( 500 , 300 , 100 , Red , TRUE) ; // 円を描画(塗りつぶし) WaitKey() ; // キーの入力待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
6. 文字を表示する。
文字を表示するにはDrawString関数を使います。表示させたい文字列の左上の座標(x,y)に文字列Stringを
表示させたい場合は
DrawString( x , y , *String , Color_Flag);
となります。
Color_Flagについては5節で説明した通りです。
では
(0,0)に「hello! DX Library!」
(100,100)に「こんにちは! DXライブラリ!」
と表示させてみましょう。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int White; White = GetColor( 255 , 255 , 255 ) ; // 白色の値を取得 DrawString( 0, 0, "hello! DX Library!" , White); //文字列表示 DrawString(100,100, "こんにちは! DXライブラリ!" , White);//文字列表示 WaitKey() ; // キーの入力待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
7. 書式付で文字を表示する。
先ほどの書き方では計算結果などが表示できませんでした。
今度は書式付で表示してみましょう。
書式付で表示できるDrawFormatString関数は先ほどの関数の引数の後ろに変数を持った関数です。
引数の順番がちょっと変わっているので見て見ましょう。
DrawFormatString( x , y , Color_Flag , "文字列" , 変数,,,,,,,
);//文字列表示
Color_Flagについては前述どおりです。文字列の中に変換指定文字を書いた文だけ後ろに変数を書きます。
サンプルではa=10; b=20; とし、これらの計算結果を表示します。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードにする。 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int a=10,b=20,White; White = GetColor( 255 , 255 , 255 ) ; // 白色の値を取得 DrawFormatString( 0, 0, White , "a=%d" , a );//文字列表示 DrawFormatString(100,100, White , "a+b=%d" , a+b);//文字列表示 WaitKey() ; // キーの入力待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
8. 時間を取得する。
時間を取得する関数はGetNowCount();関数を使います。この関数に引数は無く、返り値に、
パソコンが起動してからの秒数が単位ミリで返ってきます。
1秒=1000ミリ秒です。ですから
処理をする前にこの関数で時間を計っておき、
処理が終わってからこの関数で時間を計り、後者から前者を引けば、処理にかかった時間が測定できます。
今回はa++;という処理を10万回行った時の処理時間を表示させて見ましょう。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードにする。 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int i,a=0,time,White; White = GetColor( 255 , 255 , 255 ) ; // 白色の値を取得 time = GetNowCount() ; // 現在経過時間を得る DrawFormatString(0, 0, White , "立ち上がってから%dミリ秒", time);//文字列表示 for(i=0;i<100000;i++) //10万回a++;を実行 a++; time = GetNowCount() - time; //現在の経過時間から先ほどの経過時間を引く DrawFormatString(0,100, White , "計算時間%dミリ秒" , time);//文字列表示 WaitKey() ; // キーの入力待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
実行結果は個人によってそれぞれ違う値になります。
この280782250ミリ秒という事はこれは約280782秒であり、4679分であり、77時間です。
つまりこれはパソコンをつけてから77時間が経過している事になります。
この結果から「つけすぎだろオイ」という事がわかるわけです。
計算時間は私のパソコンでは3ミリ秒、つまり0.003秒で10万回の計算を終えた事がわかります。
9. 輝度をセットする。
輝度とは、明るさの事です。ですから輝度を下げれば表示は暗くなります。輝度を設定する関数は
SetDrawBright関数を使います。この関数の引数は0〜255のRGB値になっており、
赤だけ明るく、青だけ明るくといった事も出来ます。
(R,G,B)=(赤,緑,青)ですので描画輝度を赤のみにセットしたい時は SetDrawBright(
255 , 0 , 0 ) ; 。
全体的に半分の明るさにしたい時はSetDrawBright( 128 , 128 , 128 ) ; にします。
なお、 SetDrawBright( 255 , 255 , 255 ) ; は元の明るさです。元画像以上に明るくする事は出来ません。
では、左から順に
・普通に表示した場合
・赤以外の輝度を下げた場合
・全体的に半分の明るさにした場合
で3つ並べて表示してみます。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 SetDrawBright ( 255 , 255 , 255 ) ; //元の明るさにセット LoadGraphScreen( 0 , 0 , "char.png" , TRUE ) ;//画像を表示。 SetDrawBright ( 255 , 0 , 0 ) ; //赤以外の明るさを0にセット LoadGraphScreen( 200 , 0 , "char.png" , TRUE ) ;//画像を表示。 SetDrawBright ( 128 , 128 , 128 ) ; //明るさを半分にセット LoadGraphScreen( 400 , 0 , "char.png" , TRUE ) ;//画像を表示。 WaitKey() ; // 結果を見るためにキー待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
10. 入力状態を取得する。
現在何のキーが押されているかチェックするにはCheckHitKey関数を使用します。
この関数には任意のキーコードを渡してやる事で任意のキーの入力状態が0か1で返ってます。
返り値が0なら入力されていない。返り値が1なら入力されている。
ということです。エンターのキーコードは「KEY_INPUT_RETURN」です。
つまりCheckHitKey( KEY_INPUT_RETURN )==1ならエンターが押されている事が判ります。
他のキーコードについては本家DXライブラリ関数解説ページをご覧下さい。
キーコード表
サンプルでは、エンターが押されると処理を終える処理を行っています。
ループを行う場合はエラーが起こった時に無限ループになってしまわないように
エラーが起きていないか監視する関数ProcessMessage()という関数でエラーが起きていないかチェックしましょう。
この関数は返り値が-1の時、エラーが起きている事を示します。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int White ; White = GetColor( 255 , 255 , 255 ) ;//色の取得 DrawString( 230 , 240 , "ENTERキーを押すと終了します。" , White ); // Zキーの入力待ち while( CheckHitKey( KEY_INPUT_RETURN ) == 0 ){//ENTERが押されているかチェック if( ProcessMessage() == -1 ) break ; // エラーが発生したらループを抜ける } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
11. 全ての入力状態を取得する。
先ほどの関数ではどれか1つの取得状態しか得る事が出来ませんでした。
もしも大量にキー入力をチェックしたければ何度もCheckHitKey関数を呼ばなければなりません。
しかしCheckHitKey関数はとても無駄の多い関数のため、何度も呼ぶのは良くありません。
そこである瞬間のキー入力を全て一度にチェックしてくれる関数があります。
その関数はGetHitKeyStateAll関数といい、配列に全てのキー入力データを格納してくれます。
例えばエンターが押されているか調べる場合、使い方はこのようになります。
char Buf[ 256 ] ;
GetHitKeyStateAll( Buf ) ;
if( Buf[ KEY_INPUT_RETURN ] == 1 )
printf("エンターが押されています。\n");
まず、全てのキー入力状態のデータを格納するための配列をキャラ型で要素256個用意しましょう。
ここで先ほどの配列のポインタを引数にして関数をよんでやります。
すると配列Bufには全てのキー入力データが入ります。
(((実は先ほどのキーコードは0〜255の数字に対応しているんです。
ですから配列要素に先ほどのキーコードを指定してやる事で、そのキー入力状態がわかるようになっています。
サンプルではZキー、Xキー、スペースキーが全て押されていると処理が終わるようになっています。
注:キーボードによっては3つ同時にキーが押せない場合がありますのでご注意下さい。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int White ; char KeyBuf[ 256 ] ; White = GetColor( 255 , 255 , 255 ) ;//色の取得 DrawString( 170 , 240 , "Z,X,スペースを同時に押すと終了します。" , White ); while( 1 ){ GetHitKeyStateAll( KeyBuf ) ; // すべてのキーの状態を得る if( KeyBuf[ KEY_INPUT_Z ] == 1 && KeyBuf[ KEY_INPUT_X ] == 1 && KeyBuf[ KEY_INPUT_SPACE ] == 1 ) break ; if( ProcessMessage() == -1 ) break ;//エラーが起きたら終了 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
12. 画像を動かす。
ゲームは画像が動かなきゃ始まりませんよね。次は画像を動かしてみましょう。
for文によって、xの座標を少しずつ動かしていきます。急に動かないように
Sleep関数を使用します。Sleep関数はミリ秒を引数にして、引数分だけ処理を止めます。
例えばSleep(10);とすれば10ミリ秒すなわち0.01秒だけ処理が止まります。
サンプルでは0.01秒ずつx座標を1増やしながら表示し、x座標が300以上になったらブレイクしています。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int i ; for(i=0;i<300;i++){ LoadGraphScreen( i , 0 , "char.png" , TRUE ); //画像を描画 Sleep(10); //10ミリ秒待つ } WaitKey(); // 何かキー入力があるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
画像は動いていますが前に描画した画像が残ってしまっています。つまり1回書いたら消さないといけないわけですね。
そこで画面に書いたデータを全部消す関数ClearDrawScreen()関数を使用します。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int i ; for(i=0;i<300;i++){ ClearDrawScreen(); // 画面に描画したデータを消す LoadGraphScreen( i , 0 , "char.png" , TRUE ); //画像を描画 Sleep(10); // 10ミリ秒待つ } WaitKey(); // 何かキー入力があるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
注意)黄色で書かれている文字や線は表示されません。モーションが画像では伝えにくいために書いたものです。
このプログラムでは確かにキャラは動いて見えますが、画像がやけにチラチラして見えますね。
これはリフレッシュレートが合っていないために起こるものです。リフレッシュレートとはなにか?
お使いのモニタによって様々ですが、だいたい1秒間に60回位モニタはデータを書き換えて表示しています。
このことを「リフレッシュレートが60Hz(ヘルツ)だ」といいます。
1秒間に60回表示するということは1秒を60で割ると16〜17ミリ秒です。つまり16〜17ミリ秒に1回モニタは
画面に表示しているにもかかわらず、プログラムでは10ミリ秒に1度描写しているため、このような事が起こります。
その為「裏画面処理」を行う必要があります。
13. 裏画面処理をして画像を動かす。
先ほどのちらちらした表示を解消するために裏画面処理をします。
目に見える画面を表画面とし、データの書き換えの作業をする、擬似的な画面を裏画面とします。
裏画面で次に表示すべき画面のデータを計算、処理し、全部処理が終わったら、リフレッシュレートと
合わせて裏画面を表画面に反映させるという作業を行う必要があります。
複雑なようですがプログラムは簡単です。
先ほどのプログラムは全て表画面に描写したり画面をクリアしたりしていましたが、
描画先を裏画面にする関数SetDrawScreenを使います。
SetDrawScreen( DX_SCREEN_BACK ) ; これで描画先を裏画面へ
SetDrawScreen( DX_SCREEN_FRONT ) ; これで行が先を表画面へ
設定します。今回は裏画面を使用するので上を使います。
裏画面というのは計算だけをする領域で、実際にモニタにはうつりませんから、
計算した結果をモニタに表示する必要があります。裏画面のデータを表画面に反映させる関数は
ScreenFlip() ;を使います。
裏画面で描画してはScreenFlipで反映・・ 裏画面で描画してはScreenFlipで反映・・を繰り返す事で
チラチラした画面を解消する事が出来るのです。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1 ; // DXライブラリ初期化処理 SetDrawScreen( DX_SCREEN_BACK ) ; // 描画先を裏画面に設定 int i ; for( i = 0 ; i < 300 ; i ++ ) { ClearDrawScreen(); // 裏画面のデータを全て削除 LoadGraphScreen( i, 0, "char.png" , TRUE ) ; //裏画面へ画像を描写 ScreenFlip() ; // 裏画面データを表画面へ反映 } WaitKey(); // 何かキー入力があるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
注意)黄色で書かれている文字や線は表示されません。モーションが画像では伝えにくいために書いたものです。
ほら、画面はちらつきませんね。 ScreenFlipがリフレッシュレートに合わせて裏画面を表示してくれているおかげです。
リフレッシュレートにあわせているという事は60ヘルツのモニタの場合は16〜17ミリ秒ScreenFlip関数で処理が
止まっているという事になります。だから今回Sleep関数を使わなくても画像が急に進む事が無かったんですね。
最初に紹介した画像を表示する関数LoadGraphScreen()では、毎回ファイルの読み込みをしていますが
同じ画像ファイルを毎回読み込ませるのは無駄もあり、処理時間の遅れに繋がります。
そこであらかじめ画像データを読み込ませ、その画像データのハンドル(画像の識別番号)で画像を扱えば
とても楽に早く処理が出来ます。今回はハンドルをint型の変数に代入して画像を表示する方法を紹介します。
ゲームでは全てこの方法で画像を表示します。
ハンドルを受け取るにはLoadGraph関数を使用し、LoadGraph("画像のファイル名");の返り値を変数に代入すればよいのです。
ハンドルを使った画像の表示にはDrawGraph関数を使用し、
DrawGraph( x , y , ハンドル , Fill_Flag);
と書きます。ハンドルにはint型の先ほど格納したハンドルを指定し、Fill_Flagは今まで通り透過するかどうかのフラグです。
サンプルではchar.pngのハンドルをint型変数imageに代入し、表示しています。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1 ; // DXライブラリ初期化処理 int image; //ハンドルを受け取るためのint型変数を宣言 image = LoadGraph( "char.png" ) ; //char.pngのハンドルを代入 DrawGraph( 0 , 0 , image , FALSE ) ; //画像を表示 WaitKey(); //何かキー入力があるまで待つ DxLib_End() ; //DXライブラリの終了処理 return 0 ; }
実行結果
方法は違いますが結果として3節と同じ事をしたので実行結果は3節と同じになります。
15. 音楽を流す。
DXライブラリではたった1行の処理で音楽が流せます。
音楽形式にはmidiファイルやwav,ogg,mp3と色々ありますが、「mid」と「wav,ogg,mp3」は違う扱いをします。
midiファイルを演奏する時はPlayMusic関数を使用します。
wav,ogg,mp3ファイルを演奏する時はPlaySound関数を使用します。両者とも、引数は同じで
PlayMusic | ( "音楽ファイルの場所" , 再生タイプ ) ; |
PlaySound | ( "音楽ファイルの場所" , 再生タイプ ) ; |
となっています。再生タイプは
DX_PLAYTYPE_NORMAL | : ノーマル再生 |
DX_PLAYTYPE_BACK | : バックグラウンド再生 |
DX_PLAYTYPE_LOOP | : ループ再生 |
の3種類が指定できます。
ノーマル再生とは、再生している間処理を返さない、再生している間は処理が止まった状態にするタイプです。
バックグラウンド再生とは、先ほどとは違い、再生を始めると同時に処理を返すタイプです(普通こちらを使います。)
ループ再生とは再生を止めるまで永遠にループ再生するタイプです。
再生を止めるには
PlayMusic関数に対しては StopMusic(); で
PlaySound関数に対しては StopSound(); で止める事が出来ます。引数はvoid(無し)です。
ではそれぞれの再生方法についてサンプルプログラムで見ていきましょう。
前者がmidi用、後者がwav,ogg,mp3用再生プログラムです。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモード if( DxLib_Init() == -1 ) return -1; // 初期化 PlayMusic( "test.mid" , DX_PLAYTYPE_BACK ) ; // test.midのバックグラウンド演奏 WaitKey() ; DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
音声なので省略
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモード if( DxLib_Init() == -1 ) return -1; // 初期化 PlaySound( "test.wav" , DX_PLAYTYPE_BACK ) ; //test.wavのバックグラウンド演奏 WaitKey() ; // キーが押されるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
音声なので省略
16. メモリに音楽を読み込んでを流す。
画像の時と同じ原理で、音声もあらかじめデータを読み込んでからハンドルによって再生する方が無駄がありません。
音声データを読み込み、その識別番号であるハンドルによって再生を行ってみましょう。
音声ファイルデータを読み込み、ハンドルを生成する関数はLoadSoundMem関数です。この関数はint型で返ってくるので
返り値をint型変数に格納しましょう。
以下のサンプルプログラムではtest.wavファイルをハンドルを使用して再生しています。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウィンドウモード if( DxLib_Init() == -1 ) return -1; // 初期化 int Sound ; Sound = LoadSoundMem( "test.wav" ); //wavファイルを読み込み、ハンドルをint型変数へ格納 PlaySoundMem( Sound , DX_PLAYTYPE_BACK );//バックグラウンド再生 WaitKey() ; // キーが押されるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
音声なので省略
基本はここまでです。お疲れ様でした☆引き続き、ゲーム入門編へお進みください。
〜ゲーム入門編〜
入門編ではゲームを作成するときの基本的なプログラミング方法を紹介します。
サンプルプログラムを元に様々な処理を実現させるプログラムの作り方を紹介します。
サンプルプログラムの複雑化、長文化を防ぐためになるべく短くサンプルを書いています。
そのため、本来行うべき終了処理、例外処理などは極力書いていません。
例えば、画像を段々右に移動するプログラムは画面の外までいけば表示しないようにプログラムする必要がありますが
サンプルでは、複雑化、長文化してしまうこのような条件処理は極力書いていません。
あくまで、サンプルを説明する場ですからね。状況にあった例外処理はご自分で付け足してください☆
17. 画像を分割して読み込む
キャラは画面上を動くためには、普通16枚画像が必要です。キャラが10人いただけで画像数は160枚になってしまい、
160個のファイルを個別に読み込んでいたのでは、読み込み時間も遅くなり、コードも無駄に長くなってしまいますよね。
そこで、あらかじめくっつけた画像をプログラムで分割してしようじゃないかと考えるわけです。
今まで使ってきた画像はいかにも将来分割されそうな感じをかもし出していましたね。
今回分割してみましょう。
まずいつも使っているchar.pngは1個のキャラが(32 x 32)ピクセルの画像が横4個、縦4個あつまった計16個の画像の集まりです。
分割するための関数はLoadDivGraphを使います。この関数の引数はこうなっています。
int LoadDivGraph( char *FileName , int AllNum , int XNum , int YNum , int
XSize , int YSize , int *HandleBuf ) ;
パッと見難しそうに見えますが、見たら簡単なんでまず見てください。
FileName : 分割読み込みする画像ファイル文字列のポインタ
AllNum : 画像の分割総数
XNum ,YNum : 画像の横向きに対する分割数と縦に対する分割数
SizeX ,SizeY : 分割された画像一つの大きさ
HandleBuf : 分割読み込みして得たグラフィックハンドルを保存するint型の配列へのポインタ
これを判りやすく書くと
char.pngは1個のキャラが(32 x 32)ピクセルの画像が横4個、縦4個あつまった計16個の画像の集まりですから、
今image[16]という配列に画像ハンドルをいれていくためには
int image[16];
LoadDivGraph( "ファイルの場所" , 16 , 4 , 4 , 32 , 32 , image
) ;
このように書きます。格納される順番はこのようになります。[ ]は配列要素数を表しています。
[0] | [1] | [2] | [3] |
[4] | [5] | [6] | [7] |
[8] | [9] | [10] | [11] |
[12] | [13] | [14] | [15] |
つまり一番右上の画像は[3]に画像ハンドルが入るということですね。
この事を踏まえ、サンプルを見てみましょう。
ここでは配列要素[12]に入っているキャラを表示させて見ましょう。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image );//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 DrawGraph( 320 , 240 , image[12] , TRUE ) ;//[6]の画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
18. 構造体でキャラクタデータを作る
キャラクタは大抵 「x座標、y座標、現在の表示画像ハンドル」という3つのデータを最低でもセットで持っています。
いちいち別々にx,y,imageのデータを宣言していくのはめんどうですし、セットで扱いたいものです。そこで構造体を利用します。
typedef struct{ int x,y,img; }ch_t;
このように構造体を宣言する事で、ch_tという名前のx,y,imgセットの構造体が用意できます。
もし構造体についてわからなければgoogleで「構造体」で探して勉強してください。
これを使って先ほどの表示をしてみましょう。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" typedef struct{ int x,y,img; }ch_t; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =240; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image );//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 ch.img=image[12]; //表示画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//[12]の画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
19. キャラをキー入力によって移動させる。
いよいよ自分の操作でキャラを動かします。
今回は11節の「キー入力」、18節のプログラムを利用しますから
判らない人はそちらをご覧ください。
左上から順に右へ[0],[1],[2],,,,数えていくと、[12]は右向きのキャラの一番左にある画像になります。
この右向きの画像をキー入力が「右」なら右へ進ませるというプログラムを書きます。
キー入力が「右」ならch.x++;を行います。
サンプルプログラムは右にしか動きません。
#include "DxLib.h" typedef struct{ int x,y,img; }ch_t; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =240; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if( Key[ KEY_INPUT_RIGHT ] == 1 ) //右ボタンが押されたら ch.x++ ; //xの値を1増やす ch.img=image[12]; DrawGraph( ch.x , ch.y , ch.img , TRUE ) ; ScreenFlip(); } DxLib_End(); return 0; }
実行結果
注意)黄色い線は表示されません。
20. キャラを一区間単位で移動させる。
次に、皆さんはRPGゲームをされていて、世界がキャラ1つ分位の大きさの小さな四角形の単位で出来ていることを感じませんでしたか?
1回方向キーを押すと、ある程度勝手に歩いてとまりますよね。キャラ1個分くらい。そうなんです。
全て(一般的に)32x32ピクセルを1区分として出来ているのです。では1回「右」ボタンを押すと、32ピクセルは勝手に歩くように
プログラムしてみましょう。
このプログラムを実現するコツは x % 32 != 0 という文章です。意味がわかりますでしょうか。
x座標が32で割り切れなかったらという意味です。(直訳はxを32で割った余りが0でなかったら)
1度「右」キーを押すと少しキャラは右に移動します。32ピクセル区間歩かせたいのですから32で割り切れる位置まで行かせればいいわけです。
キーを離しても、32で割り切れない区間は勝手に移動するようにプログラムすればいいわけですね。
先ほどのプログラムに
else if(x%32!=0)
x++;
この行が入っただけです。
#include "DxLib.h" typedef struct{ int x,y,img; }ch_t; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =240; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if( Key[ KEY_INPUT_RIGHT ] == 1 ) //右ボタンが押されたら ch.x++ ; //xの値を1増やす else if(ch.x%32!=0) //32で割り切れない区間はオートで進む ch.x++; ch.img=image[12]; DrawGraph( ch.x , ch.y , ch.img , TRUE ) ; //[6]の画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
21. キャラを一区間歩かせる。
今度はテクテク歩かせてみましょう。以下のように画像をループさせたいわけですよね。
[12] -> [13] -> [14] -> [15] -> [12] ->
これを32ピクセルの1区間で1ループさせたいのです。つまり現在のキャラのx座標を32で割ったあまりが
0〜7ピクセルは -> [12]
8〜15ピクセルは -> [13]
16〜23ピクセルは -> [14]
24〜31ピクセルは -> [15]
という対応を持たせたいのです。「割ったあまり」という考え方はゲームプログラムの中ではとても重要です。
例えば32ピクセルを1区間で考えたいわけですが、そうすると、2ピクセル目も34ピクセル目も32ピクセルと言う区間の単位としては
左から2ピクセル目と言う意味では同じことです。つまりx座標を32で割ったあまりを計算する事で区間に区切れると言う事です。
[12] -> [13] -> [14] -> [15] -> [12] ->
の画像のループは
[0+12] -> [1+12] -> [2+12] -> [3+12] -> [0+12] ->
と言う事です。0〜3でループする変数を用意するにはどうしたらいいでしょうか。
現在32ピクセルで1区間にしています。これを4つに分けたいんです。と言う事は、32を8で割ればいい事がわかります。
(ch.x%32)/8
つまりこういうことですよね。ですから現在表示すべき画像は
ch.img=image[(ch.x%32)/8 +12];
で表示できます。
これをサンプルで確認してみましょう。
#include "DxLib.h" typedef struct{ int x,y,img; }ch_t; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =240; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image );//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if( Key[ KEY_INPUT_RIGHT ] == 1 ) //右ボタンが押されたら ch.x++ ; //xの値を1増やす else if(ch.x%32!=0) ch.x++; ch.img=image[(ch.x%32)/8 +12]; //現在のx座標にあった画像を指定 DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//[6]の画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
注意)黄色い線,文字は表示されません。
22. キャラを4方向に歩かせる。
水平方向が出来上がったので、鉛直方向を加えてやればいいだけです。しかも理解すればいいのは1行だけ
ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4];
これです。ちょっと見た目よくわかりませんよね。ここで、32x32を一区間とする格子点だけを通ると言う事はどういうことか考えて見ましょう。
xが32の倍数ではないとき、yは必ず32の倍数。
yが32の倍数ではないとき、xは必ず32の倍数。
このことが成り立つと言う事ですね?だって32ずつの区間でしか歩けないわけですから。
だったら、ch.x%32+ch.y%32この計算をする時、どちらかは必ず0になり、立ち止まっている時は両方0になるということです。
21節のプログラムにch.x%32+ch.y%32を加えただけで、4方向のキャラ画像の計算を実現できます。
#include "DxLib.h" typedef struct{ int x,y,img,muki,walking_flag; }ch_t; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =160; ch.walking_flag=0; ch.muki=3; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if(ch.x%32==0 && ch.y%32==0){ //座標が32で割り切れたら入力可能 ch.walking_flag=1; //歩くフラグを立てる。 if ( Key[ KEY_INPUT_UP ] == 1 ) //上ボタンが押されたら ch.muki=0; //上向きフラグを立てる else if( Key[ KEY_INPUT_LEFT ] == 1 ) //左ボタンが押されたら ch.muki=1; //左向きフラグを立てる else if( Key[ KEY_INPUT_DOWN ] == 1 ) //下ボタンが押されたら ch.muki=2; //下向きフラグを立てる else if( Key[ KEY_INPUT_RIGHT] == 1 ) //右ボタンが押されたら ch.muki=3; //右向きフラグを立てる else //何のボタンも押されてなかったら ch.walking_flag=0; //歩かないフラグを立てる } if(ch.walking_flag==1){ //歩くフラグが立っていたら if (ch.muki==0) //上向きならch.y座標を減らす ch.y--; else if(ch.muki==1) //左向きならch.x座標を減らす ch.x--; else if(ch.muki==2) //下向きならch.y座標を増やす ch.y++; else if(ch.muki==3) //右向きならch.x座標を増やす ch.x++; } ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4]; //画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
23. 行けない所を作る。〜1D〜
ダンジョンの中には進めない場所もありますよね。次は進めない場所をどうやって判定するか、勉強していきましょう。
まず、せっかく32x32ピクセルの範囲に1単位を定義したんですから、その区間を利用しましょう。
2次元で考える前にまず、1次元で考えて見ます。
横方向に32x32ピクセル単位で分割し、その中を歩いている感じを思い浮かべてください。
その区間が0の場所は通れるとし、1の場所は通れないと考えれば、通れない場所が作れますね。
横幅は640ピクセルありますから、32で割ると、20個の区間があります。ですから変数としては図の通りだと
int hantei[20] = { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1} ;
こういう風に作ればいいのです。区間の大きさは1つ32ピクセルですから
現在のx座標を32で割れば、現在いる区間の配列要素番号になります。
次に進もうとする方向の区間の配列要素が1ならば進めない、そんな判定をする関数を書いてみましょう。
int can_or_cannot(int x,int y,int muki){ if(muki==1) if(hantei[x/32+1]==1) return 1; if(muki==3) if(hantei[x/32-1]==1) return 1; return 0; }
リターン1は進めない、リターン0は進める事を意味します。
右向き進行なら配列要素+1で判定すればいいし、左向きなら配列要素-1で判定すればいいのです。
では以下のサンプルプログラムを見てみましょう。
少々長いプログラムですが、新しく書いたcan_or_cannot関数と、それを呼んでいる部分しか変更していないので
そこだけ見てください。
#include "DxLib.h" typedef struct{ int x,y,img,muki,walking_flag; }ch_t; int hantei[20] = { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 } ; int can_or_cannot(int x,int y,int muki){//進む方向が通れるか通れないかを判定 if(muki==1)//左向きなら if(hantei[x/32-1]==1)//通れないなら return 1;//エラー if(muki==3)//右向きなら if(hantei[x/32+1]==1)//通れないなら return 1;//エラー return 0;//正常 } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16]; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =160; ch.walking_flag=0; ch.muki=3; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if(ch.x%32==0 && ch.y%32==0){ //座標が32で割り切れたら入力可能 ch.walking_flag=1; //歩くフラグを立てる。 if ( Key[ KEY_INPUT_UP ] == 1 ) //上ボタンが押されたら ch.muki=0; //上向きフラグを立てる else if( Key[ KEY_INPUT_LEFT ] == 1 ) //左ボタンが押されたら ch.muki=1; //左向きフラグを立てる else if( Key[ KEY_INPUT_DOWN ] == 1 ) //下ボタンが押されたら ch.muki=2; //下向きフラグを立てる else if( Key[ KEY_INPUT_RIGHT] == 1 ) //右ボタンが押されたら ch.muki=3; //右向きフラグを立てる else //何のボタンも押されてなかったら ch.walking_flag=0; //歩かないフラグを立てる if(ch.walking_flag==1) //もし歩くなら if(can_or_cannot(ch.x,ch.y,ch.muki)==1)//行き先が歩けないなら ch.walking_flag=0; //歩かないフラグを立てる。 } if(ch.walking_flag==1){ //歩くフラグが立っていたら if (ch.muki==0) //上向きならch.y座標を減らす ch.y--; else if(ch.muki==1) //左向きならch.x座標を減らす ch.x--; else if(ch.muki==2) //下向きならch.y座標を増やす ch.y++; else if(ch.muki==3) //右向きならch.x座標を増やす ch.x++; } ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4]; //画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
注意)黄色い文字や線は表示されません。
左右とも、配列が1にあたる部分でとまります。
24. 行けない所を作る。〜2D〜
目がマチマチするような画像ですが、先ほどの概念を2Dで適応してみましょう。
1の場所が行けない場所、0の場所が行ける場所。概念は説明いりませんね、先ほどの通りです。
2次元配列の宣言・初期化の仕方は3x3配列に
123
456
789
と格納したい場合、
int hantei[3][3]={
{1,2,3},
{4,5,6},
{7,8,9}
};
このように書くことが出来ます。
左右方向に移動するときに影響する配列要素は [ ][ ● ] この部分です。
上下方向に移動するときに影響する配列要素は [ ● ][ ] この部分です。
つまり配列要素は[y/32][x/32]であらわせることがわかります。
ではサンプルプログラムを見てください。
配列の宣言と、can_or_cannnot関数しか変更していませんのでそこだけ見てください。
#include "DxLib.h" typedef struct{ int x,y,img,muki,walking_flag; }ch_t; int hantei[15][20] = { { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,1,1,1,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, }; int can_or_cannot(int x,int y,int muki){//進めるかを判定する if(muki==0)//上向きなら if(hantei[y/32-1][x/32]==1)//進めるか判定 return 1;//エラー if(muki==1)//左向きなら if(hantei[y/32][x/32-1]==1) return 1; if(muki==2)//下向きなら if(hantei[y/32+1][x/32]==1) return 1; if(muki==3)//右向きなら if(hantei[y/32][x/32+1]==1) return 1; return 0;//正常 } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16],i,j; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =160; ch.walking_flag=0; ch.muki=3; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 /*白い壁を描画*/ for(i=0;i<15;i++) for(j=0;j<20;j++) if(hantei[i][j]==1) DrawBox(j*32,i*32,(j+1)*32,(i+1)*32,GetColor(255,255,255),TRUE); if(ch.x%32==0 && ch.y%32==0){ //座標が32で割り切れたら入力可能 ch.walking_flag=1; //歩くフラグを立てる。 if ( Key[ KEY_INPUT_UP ] == 1 ) //上ボタンが押されたら ch.muki=0; //上向きフラグを立てる else if( Key[ KEY_INPUT_LEFT ] == 1 ) //左ボタンが押されたら ch.muki=1; //左向きフラグを立てる else if( Key[ KEY_INPUT_DOWN ] == 1 ) //下ボタンが押されたら ch.muki=2; //下向きフラグを立てる else if( Key[ KEY_INPUT_RIGHT] == 1 ) //右ボタンが押されたら ch.muki=3; //右向きフラグを立てる else //何のボタンも押されてなかったら ch.walking_flag=0; //歩かないフラグを立てる if(ch.walking_flag==1) //もし歩くなら if(can_or_cannot(ch.x,ch.y,ch.muki)==1)//行き先が歩けないなら ch.walking_flag=0; //歩かないフラグを立てる。 } if(ch.walking_flag==1){ //歩くフラグが立っていたら if (ch.muki==0) //上向きならch.y座標を減らす ch.y--; else if(ch.muki==1) //左向きならch.x座標を減らす ch.x--; else if(ch.muki==2) //下向きならch.y座標を増やす ch.y++; else if(ch.muki==3) //右向きならch.x座標を増やす ch.x++; } ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4]; //画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
25a. 敵に遭遇させる-1。
RPGをしていると、ダンジョンを歩くと敵に遭遇して戦闘シーンに入りますよね。あれをやってみましょう。
簡単です。1マスあるくごとに、乱数を呼んで来て、乱数が0なら敵に遭遇した事にすればいいのです。
以下のサンプルプログラムの赤い部分を見てください。
この部分にさしかかったとき、歩くフラグが立っていると言う事は、その区間で歩き始めたと言う事ですね。
この時乱数を呼んで来て、一定の確率で敵に遭遇させればいいのです。
サンプルでは4としているので、1/5の確率で敵に遭遇します。つまり5区間歩くと1回敵に遭遇する割合と言う事です。
#include "DxLib.h" typedef struct{ int x,y,img,muki,walking_flag; }ch_t; int hantei[15][20] = { { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,1,1,1,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, }; int can_or_cannot(int x,int y,int muki){//進めるかを判定する if(muki==0)//上向きなら if(hantei[y/32-1][x/32]==1)//進めるか判定 return 1;//エラー if(muki==1)//左向きなら if(hantei[y/32][x/32-1]==1) return 1; if(muki==2)//下向きなら if(hantei[y/32+1][x/32]==1) return 1; if(muki==3)//右向きなら if(hantei[y/32][x/32+1]==1) return 1; return 0;//正常 } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16],i,j; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =160; ch.walking_flag=0; ch.muki=3; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 /*白い壁を描画*/ for(i=0;i<15;i++) for(j=0;j<20;j++) if(hantei[i][j]==1) DrawBox(j*32,i*32,(j+1)*32,(i+1)*32,GetColor(255,255,255),TRUE); if(ch.x%32==0 && ch.y%32==0){ //座標が32で割り切れたら入力可能 ch.walking_flag=1; //歩くフラグを立てる。 if ( Key[ KEY_INPUT_UP ] == 1 ) //上ボタンが押されたら ch.muki=0; //上向きフラグを立てる else if( Key[ KEY_INPUT_LEFT ] == 1 ) //左ボタンが押されたら ch.muki=1; //左向きフラグを立てる else if( Key[ KEY_INPUT_DOWN ] == 1 ) //下ボタンが押されたら ch.muki=2; //下向きフラグを立てる else if( Key[ KEY_INPUT_RIGHT] == 1 ) //右ボタンが押されたら ch.muki=3; //右向きフラグを立てる else //何のボタンも押されてなかったら ch.walking_flag=0; //歩かないフラグを立てる if(ch.walking_flag==1) //もし歩くなら if(can_or_cannot(ch.x,ch.y,ch.muki)==1)//行き先が歩けないなら ch.walking_flag=0; //歩かないフラグを立てる。 if(ch.walking_flag==1) if(GetRand(4)==0) printfDx("敵"); } if(ch.walking_flag==1){ //歩くフラグが立っていたら if (ch.muki==0) //上向きならch.y座標を減らす ch.y--; else if(ch.muki==1) //左向きならch.x座標を減らす ch.x--; else if(ch.muki==2) //下向きならch.y座標を増やす ch.y++; else if(ch.muki==3) //右向きならch.x座標を増やす ch.x++; } ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4]; //画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
25b. 敵に遭遇させる-2。
ダンジョンに敵を表示しておいて、敵に接触したら戦闘シーンに入りたいときもあります。
その時は、先ほど行けるかどうか判定するには配列要素に1を格納していました。
敵がいる位置に2を格納すれば、敵に遭遇したかどうか
判定が出来ますね。
hantei配列の中身に「2」を所々いれてみました。実行結果で青緑で表示されている区間が敵のいる区間です。
そこにさしかかったら「敵」と表示されます。
#include "DxLib.h" typedef struct{ int x,y,img,muki,walking_flag; }ch_t; int hantei[15][20] = { { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,2,0,0,0,0,0,0,0,0,2,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,2,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,2,0,0,0,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,2,1,1 }, { 1,1,1,1,1,0,2,0,0,0,1,0,2,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,1,1,1,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1 }, { 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,2,0,1,1 }, { 1,1,1,1,1,0,0,2,0,0,0,0,2,0,0,0,0,0,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }, }; int can_or_cannot(int x,int y,int muki){//進めるかを判定する if(muki==0)//上向きなら if(hantei[y/32-1][x/32]==1)//進めるか判定 return 1;//エラー if(muki==1)//左向きなら if(hantei[y/32][x/32-1]==1) return 1; if(muki==2)//下向きなら if(hantei[y/32+1][x/32]==1) return 1; if(muki==3)//右向きなら if(hantei[y/32][x/32+1]==1) return 1; return 0;//正常 } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int image[16],i,j; char Key[256]; ch_t ch; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //ウィンドウ化と初期化処理 ch.x =320; ch.y =160; ch.walking_flag=0; ch.muki=3; SetDrawScreen( DX_SCREEN_BACK ) ; //描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 /*白い壁を描画*/ for(i=0;i<15;i++){ for(j=0;j<20;j++){ if(hantei[i][j]==1) DrawBox(j*32,i*32,(j+1)*32,(i+1)*32,GetColor(255,255,255),TRUE); if(hantei[i][j]==2) DrawBox(j*32,i*32,(j+1)*32,(i+1)*32,GetColor( 0,255,255),TRUE); } } if(ch.x%32==0 && ch.y%32==0){ //座標が32で割り切れたら入力可能 ch.walking_flag=1; //歩くフラグを立てる。 if ( Key[ KEY_INPUT_UP ] == 1 ) //上ボタンが押されたら ch.muki=0; //上向きフラグを立てる else if( Key[ KEY_INPUT_LEFT ] == 1 ) //左ボタンが押されたら ch.muki=1; //左向きフラグを立てる else if( Key[ KEY_INPUT_DOWN ] == 1 ) //下ボタンが押されたら ch.muki=2; //下向きフラグを立てる else if( Key[ KEY_INPUT_RIGHT] == 1 ) //右ボタンが押されたら ch.muki=3; //右向きフラグを立てる else //何のボタンも押されてなかったら ch.walking_flag=0; //歩かないフラグを立てる if(ch.walking_flag==1) //もし歩くなら if(can_or_cannot(ch.x,ch.y,ch.muki)==1)//行き先が歩けないなら ch.walking_flag=0; //歩かないフラグを立てる。 if(hantei[ch.y/32][ch.x/32]==2) printfDx("敵"); } DrawFormatString(0,460,GetColor(0,0,0),"hantei[%d][%d]=%d" ,ch.y/32,ch.x/32,hantei[ch.y/32][ch.x/32]); //現在の位置を描画 if(ch.walking_flag==1){ //歩くフラグが立っていたら if (ch.muki==0) //上向きならch.y座標を減らす ch.y--; else if(ch.muki==1) //左向きならch.x座標を減らす ch.x--; else if(ch.muki==2) //下向きならch.y座標を増やす ch.y++; else if(ch.muki==3) //右向きならch.x座標を増やす ch.x++; } ch.img=image[(ch.x%32+ch.y%32)/8 + ch.muki*4]; //画像をセット DrawGraph( ch.x , ch.y , ch.img , TRUE ) ;//画像を描画 ScreenFlip(); } DxLib_End(); return 0; }
実行結果
26. シューティング基本
さてさて長いプログラムを見て疲れたでしょう。
次は一風変わってRPGからシューティングの基本を勉強しましょう。別にシューティングに限ったことではありません。
RPGの中でも何か弾丸が飛ぶようなシーンを作りたければ参考になることでしょう。
まず、画面に表示する玉を構造体で書きます。
struct shot{
int x,y;
int flag;
};
x、yは玉の座標。 flagは玉が発射したかどうかを示すフラグです。画面内に表示したいだけ、これを用意しましょう。
struct shot tama[10];
で宣言し、玉を10個作ります。
for(i=0;i<10;i++){
tama[i].x=640/2; tama[i].y=480;
tama[i].flag=0;
}
で初期化し、flagに0を入れます。0が発射していない状態。1が発射している状態を示します。
if(counter<5) //前にエンターを押してから5カウント未満なら
counter++; //カウントアップ
エンターを押した瞬間がcounter=0;で、順に値が増加します。
これは、エンターを押したときに連続で発射してしまわないようにする処置です。
5回処理が回ってくるまで発射されません。
else if( Key[ KEY_INPUT_RETURN ] == 1 ){//5カウント以上たっていたら
counter=0; //カウンターを戻す
for(i=0;i<10;i++){
if(tama[i].flag==0){ //発射していない玉を探し、
tama[i].flag=1; //発射フラグを立てる
break;
}
}
}
前回発射してから5カウント以上たっていて、かつエンターが押されていれば、カウンターを戻し、
発射されていないフラグの玉を捜して発射フラグを立てる。
for(i=0;i<10;i++){
if(tama[i].flag==1){ //発射している玉なら
tama[i].y-=8; //座標を8減らす
DrawGraph( tama[i].x , tama[i].y , image[1] , TRUE );//玉を描画
if(tama[i].y < -32){ //もし画面外まで来たら
tama[i].y=480; //初期値に戻し、
tama[i].flag=0; //発射フラグを戻す
}
}
}
発射フラグを探して、発射フラグのたっている玉の座標を減らし、描画する。
もし画面外に出ていたら発射フラグを戻し、初期値に戻す。
このプログラムをくっつけて完成したのがサンプルプログラムです。
#include "DxLib.h" struct shot{ int x,y; int flag; }; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int image[16],i,counter=0; //counter=発射してからのカウントをする変数。 char Key[256]; struct shot tama[10]; //tamaを10個作る。 for(i=0;i<10;i++){ //初期化処理 tama[i].x=640/2; tama[i].y=480; //座標代入 tama[i].flag=0; //飛んでいない事を示すフラグ=0 } SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(1){ ClearDrawScreen(); //裏画面のデータを全て削除 GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 if(counter<5) //前にエンターを押してから5カウント未満なら counter++; //カウントアップ else if( Key[ KEY_INPUT_RETURN ] == 1 ){//5カウント以上たっていたら counter=0; //カウンターを戻す for(i=0;i<10;i++){ if(tama[i].flag==0){ //発射していない玉を探し、 tama[i].flag=1; //発射フラグを立てる break; } } } for(i=0;i<10;i++){ if(tama[i].flag==1){ //発射している玉なら tama[i].y-=8; //座標を8減らす DrawGraph( tama[i].x , tama[i].y , image[1] , TRUE );//玉を描画 if(tama[i].y < -32){ //もし画面外まで来たら tama[i].y=480; //初期値に戻し、 tama[i].flag=0; //発射フラグを戻す } } } if( Key[ KEY_INPUT_ESCAPE ] == 1 ) break;//Escボタンが押されたらブレイク ScreenFlip() ;//裏画面データを表画面へ反映 } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 }
実行結果
注意)黄色い線は表示されません。
27. ジャンプの考え方1(物理)
物理大好きな人には、食いつく内容かもしれませんが、文型の人や数学嫌いって人には
なかなか難しく感じますよね。しかし、このサイトのスタンス、「わかりやすく」はここでも健在です!!
1から・・いや0から解説します!!文型の人のための、物理入門ページここに開設しました!
どんな数学嫌い、物理嫌いでも必ずわかります☆
理系の人でもゲームに物理を適応するのは初めて!って人も是非ご覧下さい。
この節を読む前に是非、上記リンクから物理入門ページをご覧下さい。
物理は高校で習ったけど忘れたって人、以下を引き続きご覧下さい。
では、節の内容をはじめます。
マリオのようにジャンプするアクションを作る事がありますよね。
ジャンプを厳密な方法で表現しようとすると物理の知識が必要です。
ちょっと数式が出てきますんで、「数学は大っきらいだ!」という人はスルーしてください。別に絶対知ってないと
ゲームが作れないわけじゃありません。わかりやすく説明しますから、落ち着いてみていきましょう。
物理にはとっても有名な@式とA式がありますね(下図参照)。この2式を用いて計算します。
プログラムでは常にyの座標を計算する必要が出てきますね。
@式ように時間とともに変化するyの値を常に計算する必要があります。
しかし@式のままではVo(初速度)を代入する必要があります。
描きたい放物線がある時、地点0から頂点までの距離はわかっても、初速度はわからないですよね。
プログラムで描きたい放物線は例えば「2m上まで投げ上げた時の時間とともに変化するy」とかいうときですよね。
つまり初速度はわかっておらず、初速度を代入しなければならない式はふさわしくないわけです。
ですからA式から@式に代入して、Voの無い式にしましょう。
すると図からB式が作り出せますね。
これはプログラムではこのように表せます。
y = sqrt ( 2.000 * g * y_max) * t - 0.500 * g * t * t;
今回の設定では縦が480ピクセル(のうちキャラが32ピクセル)ですから、480−32=448
y = (int) (sqrt ( 2.000 * g * y_max) * t - 0.500 * g * t * t) * 448.000
/ y_max;
こう書くことで、画面のピクセル幅と放物線のt=0の地点から頂点までの幅がピッタリになります。
それではプログラムの解説をします。
if( Key[ KEY_INPUT_RETURN ] == 1 ){
time1 = GetNowCount();
flag=1;
}
もしエンターが押されたら、飛び上がりフラグflag=1をたてる。
time1 = GetNowCount();によってエンターの押した時間をミリ秒で取得する。
if(flag==1){
time2 = GetNowCount() ; // 現在経過時間を得る
t = (double)(time2 - time1) / 1000.000;
y = (int)((sqrt ( 2.000 * g * y_max) * t - 0.500 * g * t * t ) * 480.000
/ y_max);
飛び上がりフラグが立っている時、time2に今の時間を格納し、tにミリ秒を秒に変換してdoubleで格納。
yは先ほど紹介したとおり代入する。
if(y>=0)
DrawGraph( x , 480-y , image[13] , TRUE );
else
flag=0;
}
また、y>=0の時に描画し、画面の外に出たら、飛び上がりフラグを戻す。
これらをまとめてサンプルプログラムを書いたので見てください。
#include "DxLib.h" #include <math.h> #define g 9.8067 #define y_max 2.000 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int x=320,y=480,image[16],time1,time2,flag=0; double t; char Key[256]; SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(1){ ClearDrawScreen();//裏画面のデータを全て削除 GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 if( Key[ KEY_INPUT_RETURN ] == 1 ){ //エンターが押されたら time1 = GetNowCount(); //time1にエンターが押された時の時間を格納 flag=1; //飛び上がりフラグを立てる。 } if(flag==1){ time2 = GetNowCount() ; // 現在経過時間を得る t = (double)(time2 - time1) / 1000.000; // ミリ秒を秒に変換して、エンターが押されてからの経過時間を計算 y = (int)((sqrt ( 2.000 * g * y_max) * t - 0.500 * g * t * t ) * 480.000 / y_max);//y座標を計算 if(y>=0) // 1回目に回って来たか、画面内にy座標がある時 DrawGraph( x , 480-y , image[8] , TRUE );//画像を描画 else flag=0; // 画面外に来ると、飛び上がりフラグを戻す } if( Key[ KEY_INPUT_ESCAPE ] == 1 ) break; //Escボタンが押されたらブレイク ScreenFlip() ; //裏画面データを表画面へ反映 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
注意:黄色い線と文字は表示されません。
28. ジャンプの考え方2
先ほどは鉛直方向に投げ上げでしたけど、今度は斜めに投げ上げてみましょう。
x方向は等速運動をしますから、一定の速度です。つまり
x = 定数 * 時間;
でもとまるのです。今回は仮に、
x = 500 * t; |
としておきましょう。xこの行が加わっただけで、他の部分はかわっていません。ではサンプルを見て見ましょう。
#include "DxLib.h" #include <math.h> #define g 9.8067 #define y_max 2.000 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int x,y,image[16],time1,time2,flag=0; double t; char Key[256]; SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(1){ ClearDrawScreen();//裏画面のデータを全て削除 GetHitKeyStateAll( Key ) ; if( ProcessMessage() == -1 ) break ; if( Key[ KEY_INPUT_RETURN ] == 1 ){ time1 = GetNowCount(); flag=1; } if(flag==1){ time2 = GetNowCount() ; t = (double)(time2 - time1) / 1000.000; x = t*500; y = (int)((sqrt ( 2.000 * g * y_max) * t - 0.500 * g * t * t ) * 480.000 / y_max); if(y>=0) DrawGraph( x , 480-y , image[8] , TRUE ); else flag=0; } if( Key[ KEY_INPUT_ESCAPE ] == 1 ) break;//Escボタンが押されたらブレイク ScreenFlip() ;//裏画面データを表画面へ反映 } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 }
実行結果
注意:黄色い線や文字は出ません。
29. 跳ね返りの考え方1
今度は床に跳ね返る時のモーションを考えましょう。反発係数というものをご存知でしょうか。
反発係数eは以下の式で求められます。
B式だけ見てもらったらいいです。何か難しそうな式ですが、ようは、床に跳ね返った後、速さが0.8倍になれば
反発係数も0.8というだけの事です。ですから j 回床に跳ね返った時の初速度はこのように計算できます。
#define e 0.800
v0= sqrt ( 2.000 * g * y_max);
for(i=0;i<j;i++)
v0*=e;
v0の式は前回の通りです。j 回分0.8倍された事になりますね。
ではサンプルプログラムを見てください。ほとんど前回と変わっていません。初速度の計算部分だけ変えてあります。
#include "DxLib.h" #include <math.h> #define e 0.800 #define g 9.807 #define y_max 2.000 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int y,image[16],time1,time2,flag=0,i,j; //時間を取得するtime1,time2。フラグ-flag double t,v0; char Key[256]; SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(1){ ClearDrawScreen();//裏画面のデータを全て削除 GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 if( Key[ KEY_INPUT_RETURN ] == 1 ){ time1 = GetNowCount(); flag=1; j=0; } if(flag==1){ time2 = GetNowCount() ; // 現在経過時間を得る t = (double)(time2 - time1) / 1000.000; v0= sqrt ( 2.000 * g * y_max); //初速度を計算 for(i=0;i<j;i++) //j回跳ね返った時の初速度 v0*=e; //eは反発係数 y = (int)((v0 * t - 0.500 * g * t * t ) * 480.000 / y_max); if(y>=0) DrawGraph( 320 , 480-32-y , image[8] , TRUE ); else{ DrawGraph( 320 , 480-32 , image[8] , TRUE ); time1=GetNowCount(); j++; } } if( Key[ KEY_INPUT_ESCAPE ] == 1 ) break;//Escボタンが押されたらブレイク ScreenFlip() ;//裏画面データを表画面へ反映 } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 }
実行結果
注意:黄色の文字や線は表示されません。
30. 2つ同時に画像を動かす
画像を動かす方法はもう見てきましたが、ここでおさらいしておきましょう。
画像を動かす方法には2種類ありますね。
座標を計算しては、描画し、ちょっと待って、座標を計算・・の繰り返し。
しかしこんな直線型プログラムでかいてしまったらどうでしょう。
せっかく画像1のx座標を1ずつ増やして描画するプログラムを作ったとしても、
後から画像2が入れたくなった時、簡単に入れる事が出来ません。ではこんな風に書き換えてみましょう。
こんなプログラムを書けば、画像2でも3でもいくらでもすぐに入れる事が出来ますね。
一見当たり前のようでとても重要な事なんです。上の例は1つのキャラしか動かす事が出来ない悪いプログラムの例です。
みなさん気をつけましょう。サンプルプログラムでは2つのキャラを同時に動かしています。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int x1=640-24,x2=0,image[16]; SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ;//画像を分割してimage配列に保存 while(1){ ClearDrawScreen(); //裏画面のデータを全て削除 if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 x1-=3; x2+=5; DrawGraph(x1,150,image[4] ,TRUE); //左向き画像の描画 DrawGraph(x2,350,image[12] ,TRUE); //右向き画像の描画 if(x1<0) break; //画像1のx座標が0未満になれば終了 ScreenFlip() ;//裏画面データを表画面へ反映 } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 }
実行結果
31. ランダムな数の作り方
ランダムな数を生成するには普通stdlib.hのrand()関数を利用しますよね。
しかしDXライブラリにはもっとよい乱数の生成関数があります。rand()関数は、ランダムな数に見えて実は全然
ランダムな数ではありません。初期値を設定しないと、毎回同じ値がでてしまいます。
rand()関数ででてくる数は、毎回決まっていて、
41 , 18467 , 6334 , 26500 , 19169 , 15724,,,,,,,毎回この値が出ます。
しかしDXライブラリでは、標準でいつも違った乱数が出ます。
GetRand()関数を使います。0〜引数に持たせたint型の数値までのどれかの値をリターンで返します。
例えば
x = GetRand(10) ; とすれば、xには0〜10のどれかの値が入ります。
また、乱数を初期化したいときは初期化関数SRand()を利用しましょう。初期化については32節で説明します。
では出たら目な位置に、画像を100枚描画するプログラムを書いてみます。
このプログラムは横640ピクセル、縦480ピクセルの画面である事を利用しています。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 int i,image[16]; LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ; //画像を分割してimage配列に保存 for(i=0;i<100;i++) DrawGraph(GetRand(640),GetRand(480),image[4],TRUE);//ランダムな場所に画像を描写 WaitKey(); // 何かキー入力があるまで待つ DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
32. 乱数の初期化の仕方
乱数は初期値を設定する事でいつも同じ値が取り出せます。
シューティングゲームなんかは、乱数によって、玉を発射させます。
リプレイ映像を表現したい時、動画で保存したんじゃとてつもなくデータが多くなってしまいますね。
ですから、今まさに操作しているようかにその人が操作したのと同じようにキャラを動かし、
(開始から何ミリ秒右を押したか、何ミリ秒左を押したか操作データを保存して、それにしたがって動かす)
玉もその時と同じように出せば、その時と同じ状況が再現でき、リプレイ映像が表現できますね。
このように乱数は初期値によって以前と同じ値を取り出したいときがあります。そのときにはSRand関数を使います。
引数に持たせたint型数値によって初期化されますので、同じ乱数を生成したい時は、この引数に同じ値を渡しましょう。
printfDxで表示させたい文字列はScreenFlip();を呼んだ時点で表示される事に注意して下さい。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; // ウインドウモードに変更 if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 エラーが起きたら終了 SRand(10); //乱数の初期値を10で設定。 printfDx("%d\n",GetRand(10000)); //0〜10000までの乱数を生成,画面に出力 printfDx("%d\n",GetRand(10000)); printfDx("%d\n",GetRand(10000)); printfDx("%d\n",GetRand(10000)); printfDx("\n"); SRand(10); //乱数の初期値を10で設定。 printfDx("%d\n",GetRand(10000)); //0〜10000までの乱数を生成 printfDx("%d\n",GetRand(10000)); printfDx("%d\n",GetRand(10000)); printfDx("%d\n",GetRand(10000)); ScreenFlip(); WaitKey(); //何かキー入力があるまで待つ DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
33. 選択画面の作り方。
文字列の左に矢印を描画し、今何を選択しているか示したい場合があります。
サンプルでは矢印の代わりに文字列の左に■を描画して現在選択している文字列の状態を表現しています。
サンプルで説明すると、最初「NEW GAME」が選択されているとします。
下キーを押すと、CONTINUE1が選択されます。もう一度下キーを入力するとCONTINUE2になります。
選択画面は、キーを押すごとに状態が変わるものですが、キーを押しっぱなしにするとオートで状態がかわりますね。
「下キーを押すごとに選択が下へ移動する」
「下キーを押しっぱなしにするとオートで選択が下へ移動する」
という2種類の処理をする必要があります。
これには40節の「キーがどれ位押されたかを監視する関数」を利用すると便利なので、まずそちらをご覧下さい。
そちらで紹介している関数がわかれば、41節も一緒にご覧下さい。
41節のプログラムに、上記の仕様を実装すると以下のようになります。
#include "DxLib.h" int GetHitKeyStateAll_2(int KeyStateBuf[]){ char GetHitKeyStateAll_Key[256]; GetHitKeyStateAll( GetHitKeyStateAll_Key ); for(int i=0;i<256;i++){ if(GetHitKeyStateAll_Key[i]==1) KeyStateBuf[i]++; else KeyStateBuf[i]=0; } return 0; } void char_disp(int White,int y){ DrawString( 150 , y , "■" , White ); DrawString( 170 , 100 , "NEW GAME" , White ); DrawString( 170 , 120 , "CONTINUE1" , White ); DrawString( 170 , 140 , "CONTINUE2" , White ); DrawString( 170 , 160 , "CONTINUE3" , White ); DrawString( 170 , 180 , "CONTINUE4" , White ); DrawString( 170 , 200 , "CONTINUE5" , White ); DrawString( 170 , 220 , "CONTINUE6" , White ); DrawString( 170 , 240 , "LOG OUT" , White ); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int Key[256]; int White , y=100; //色とy座標の宣言 ChangeWindowMode(TRUE);//ウィンドウモード if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化 White = GetColor( 255 , 255 , 255 ) ; //色の取得 while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){ //↑メッセージ処理 ↑画面をクリア ↑入力状態を保存 ↑ESCが押されていない char_disp(White,y); // 文字を描画 if( Key[KEY_INPUT_DOWN]==1 ||( Key[KEY_INPUT_DOWN]%5==0 && Key[KEY_INPUT_DOWN]>30)){ // たった今押したか、30カウンター以上押しっぱなしかつ5回に一度 y+=20; if(y==260) // y座標が260なら(選択が一番下なら) y=100; // 選択座標を一番上に } ScreenFlip();//裏画面反映 } DxLib_End(); return 0; }
実行結果
34. メイン関数の書き方。
メイン関数の書き方に別に決まりはありませんが、メイン関数には各種関数に渡す分配の処理だけを書くのがよいでしょう。
処理がごちゃごちゃにならないように、メイン関数はなるべくシンプルにします。
function_statusというどの関数に処理を渡せばいいかのフラグを作り、そこに整数値を代入する事で
関数へ処理を渡す判定をしましょう。function_statusが
0の時は、オープニングに関する関数へ、
1の時は、メニュー画面に関する関数へ、
2の時は、キャラがダンジョンにいるときに処理する関数へ、
3の時は、戦闘シーンを処理する関数へ、
4の時は、エンディングを処理する関数へ、
という具合にfunction_statusでどの関数に処理を渡せばいいかを判定します。
サンプルでは、各種場面で処理をする関数を多数用意し、function_statusによってメイン関数から
どの関数に渡すか判定しています。始めはfunction_statusは0で、指示に従ってキー入力するにしたがって
function_statusが変化していきます。変化するごとに、どの関数に処理を渡すかが変化していきます。
#include "DxLib.h" int function_status=0,White; char KeyBuf[ 256 ] ; void Opening(){ DrawString(100,100,"オープニング画面 (zをプッシュ)",White); if(KeyBuf[KEY_INPUT_Z]==1) function_status=1; } void Menu(){ DrawString(100,140,"メニュー画面 (xをプッシュ)",White); if(KeyBuf[KEY_INPUT_X]==1) function_status=2; } void Danjon(){ DrawString(100,180,"ダンジョン画面 (cをプッシュ)",White); if(KeyBuf[KEY_INPUT_C]==1) function_status=3; } void attack(){ DrawString(100,220,"戦闘画面 (vをプッシュ)",White); if(KeyBuf[KEY_INPUT_V]==1) function_status=4; } void Ending(){ DrawString(100,260,"エンディング画面 (bをプッシュ)",White); if(KeyBuf[KEY_INPUT_B]==1) function_status=5; } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 White = GetColor( 255 , 255 , 255 ) ; //色の取得 SetDrawScreen( DX_SCREEN_BACK ) ; // 描画先を裏画面に設定 while( 1 ){ ClearDrawScreen(); // 裏画面のデータを全て削除 GetHitKeyStateAll( KeyBuf ) ; // すべてのキーの状態を得る switch(function_status){ case 0: Opening(); break; case 1: Menu(); break; case 2: Danjon(); break; case 3: attack(); break; case 4: Ending(); break; default: DxLib_End() ; // DXライブラリ使用の終了処理 return 0; break; } if( ProcessMessage() == -1 ) break ; //エラーが起きたら終了 ScreenFlip() ; // 裏画面データを表画面へ反映 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果 (例)
35. 画面を段々暗く & 段々明るく
フェードイン、フェードアウトといって、画面を次第に明るくしたり、次第に暗くしたりといったことは、
物語が始まる時や終わるときによく用いられる手法ですね。
これは画面の輝度を使うことで実現できます。
今回はカウンターを用いて、カウントし、画面を段々明るく&段々暗くという事を行います。
画面の輝度を設定する時にはSetDrawBright( R , G , B );という関数を用います。
R,G,Bには0~255の値が入り、それぞれ赤、緑、青の輝度を示します。
SetDrawBright( 255 , 255 , 255 );
この状態は通常の状態。全て赤緑青の明るさが全て255の状態です。
今の状態から明るさを減らさない状態と覚えるといいでしょう。
輝度を245にすると通常の明るさより10減らすといった感じです。
通常の明るさより増やす事はできません。ですから
SetDrawBright( 0 , 0 , 0 );
これは真っ暗な状態です。
SetDrawBright( 0 , 0 , 0 );
↓
描画
↓
SetDrawBright( 1 , 1 , 1 );
↓
描画
↓
SetDrawBright( 2 , 2 , 2 );
↓
としていき、0〜255まであげてやれば段々明るくなりますね。
段々暗くする時はその逆を、通常の明るさに戻したい時は SetDrawBright( 255
, 255 , 255 ); と
書けばいいのです。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int c=0,White; White = GetColor( 255 , 255 , 255 ) ; //色の取得 SetDrawScreen( DX_SCREEN_BACK ) ; // 描画先を裏画面に設定 while( 1 ){ ClearDrawScreen(); // 裏画面のデータを全て削除 if(c>=0 && c<256) //cが0~256の時 SetDrawBright( c , c , c ); //段々あかるく(c= 0->255) if(c>=256 && c<400) //cが256~400の時 SetDrawBright( 255 , 255 , 255 ); //通常の明るさ if(c>=400 && c<400+256) //cが400~400+256の時 SetDrawBright( 255-(c-400) , 255-(c-400) , 255-(c-400) );//段々暗く(c= 255->0) DrawBox( 0 , 0 , 640 , 480 , White , TRUE) ; //画面全体に白い四角形を描画 c++; //cをカウントアップ if(c==400+256) break; //暗くなり終わったら終了 if( ProcessMessage() == -1 ) break ; //エラーが起きたら終了 ScreenFlip() ; // 裏画面データを表画面へ反映 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果(例)
36. ソースを短く書く(その1)
「プログラムを短く判り易く書く」というのも一つのスキルであり、後から見直すにも
ダラダラ長いプログラムよりスマートなプログラムの方が見やすいですね。
「毎回決まって書くもの」は何とか短くかけないでしょうか?
ループ処理をする時にひつようなProcessMessage()、
画面に描写されたものを削除するClearDrawScreen()、
キーボードの入力状態をKey配列に格納するGetHitKeyStateAll( Key )、
描写されたものを画面に反映するScreenFlip()、
の4つの関数は毎回書く必要があります。
後、ループから抜けるために、Escが押されたらブレイクするという処理も必要です。
これらを短く書くことを試みます。
if(0){
処理1;
}
このプログラムにおいて、処理1は実行されません。
if(1){
処理2;
}
このプログラムにおいて、処理2は必ず実行されます。
一方で、反対を意味する「!」を思い出してください。
if(!0){
処理3;
}
このプログラムにおいて、処理3は必ず実行されます。ifの中の条件文は0でなければ真となります。この事を利用しましょう。
ProcessMessage()は0が返って来たとき正常です。
ClearDrawScreen()も0が返って来たとき正常です。
GetHitKeyStateAll( Key )も0が返って来たとき正常です。
Key[KEY_INPUT_ESCAPE]も0の時入力されていません。
つまりこれら全て0じゃ無いときに、終了させたらいいですね。これらを書くと
条件分(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll(
Key ) && !Key[KEY_INPUT_ESCAPE])
こうなります。これらのどれか一つでもエラーが発生したり、ESCが入力されたりすると、条件から外れます。
こうすることでループの条件がスマートになりますね。
ウィンドウモードに変更するためのChangeWindowMode(TRUE)
と
DXライブラリの初期化処理関数DxLib_Init()も毎回書くのでひとまとまりにしてしまいましょう。
ChangeWindowMode(TRUE)はDX_CHANGESCREEN_OKが返ってくると、成功ですので、
DX_CHANGESCREEN_OKが返ってこなかったらreturnすればいいですね。
つまりこういうことです。
if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1
) return -1;
以上のことを纏めると以下のようなプログラムになります。
#include "DxLib.h" char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //初期化処理 SetDrawScreen( DX_SCREEN_BACK ); //裏画面に設定 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されていない ScreenFlip(); } DxLib_End(); return 0; }
実行結果
省略
37. ソースを短く書く(その2)
DXライブラリの初期化処理の後なら、変数の宣言と同時に関数の返り値を代入して初期化することが出来ます。
if( DxLib_Init() == -1 ) return -1; //初期化処理
↑初期化処理とはこれですね。この後なら、たとえば、画像の識別ハンドルを格納するために、
int image = LoadGraph("char.png");
このように変数の宣言と同時に格納することも出来ます。
#include "DxLib.h" char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //初期化処理 int image = LoadGraph("char.png"); SetDrawScreen( DX_SCREEN_BACK ); //裏画面に設定 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されていない DrawGraph(0,0,image,TRUE); ScreenFlip(); } DxLib_End(); return 0; }
実行結果
省略
38. αブレンド(その1)
9章で輝度の設定を行いましたが、もとある背景の上に描画する時は、上書きではなく、背景の色も含めてブレンドしながら
描画しなければなりません。背景と今から上書きする画像との色をうまくブレンドする方法をαブレンドといいます。
αブレンドは「透けてる感じ」を出すためのブレンドモードです。
ここではαブレンドを使って見ましょう。どのように違いがあるか100聞は一見に如かずなので、
とりあえず39章まで一気に実行して見てください。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 DrawBox(0, 0,640, 50,GetColor(255,255,255),TRUE); //白い四角形を描画 DrawBox(0, 50,640,100,GetColor(255, 0, 0),TRUE); //赤い四角形を描画 SetDrawBright ( 128 , 128 , 128 ) ; //半分の明るさにセット LoadGraphScreen( 0 , 0 , "char.png" , TRUE ) ; //画像を表示。 SetDrawBright ( 255 , 255 , 255 ) ; //元の明るさにリセット SetDrawBlendMode( DX_BLENDMODE_ALPHA , 128 ) ; //以後半分の明るさでαブレンドする LoadGraphScreen( 400 , 0 , "char.png" , TRUE ) ; //画像を表示。 SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ; //ブレンドモードをリセットする。 WaitKey() ; // 結果を見るためにキー待ち(『WaitKey』を使用) DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
39. αブレンド(その2)
今度はこれらの違いがもっとはっきりわかるようにフェードアウト、フェードインを繰り返してみます。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 int count=0,flag=-1; SetDrawScreen( DX_SCREEN_BACK ) ; // 描画先を裏画面に設定 while( CheckHitKey( KEY_INPUT_ESCAPE )==0 ){ //エスケープキーが入力されていない間ループ ClearDrawScreen(); // 裏画面のデータを全て削除 DrawBox(0, 0,640, 50,GetColor(255,255,255),TRUE); //白い四角形を描画 DrawBox(0, 50,640,100,GetColor(255, 0, 0),TRUE); //赤い四角形を描画 SetDrawBright ( count , count , count ) ; //countの明るさにセット LoadGraphScreen( 0 , 0 , "char.png" , TRUE ) ; //画像を表示。 SetDrawBright ( 255 , 255 , 255 ) ; //元の明るさにリセット SetDrawBlendMode( DX_BLENDMODE_ALPHA , count ) ; //以後countの明るさでαブレンドする LoadGraphScreen( 400 , 0 , "char.png" , TRUE ) ; //画像を表示。 SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ; //ブレンドモードをリセットする。 if(count>255 || count<0) flag*=-1; //count上昇モード下降モード分岐 if(flag==-1) count+=2; //インクリメント if(flag== 1) count-=2; //デクリメント if( ProcessMessage() == -1 ) break ; //エラーが起きたら終了 ScreenFlip() ; // 裏画面データを表画面へ反映 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
αブレンドだとちゃんと背景色が影響している事がわかりましたか?
キャラがす〜〜っと消えていくようなモーションを作るときは輝度の変更ではなく、αブレンドしてやらないと
いけないことが解ると思います。ブレンドモードにはαブレンドのほかにも色々あります。
本家DXライブラリのリファレンスページに詳しく乗っているのでこちらのサンプルも実行してみてください。
http://homepage2.nifty.com/natupaji/DxLib/dxfunc.html#R3N17
40. 「キーがどれ位押されたか」を判定する関数
DXライブラリ本体にはキー入力において、「押しているか」「押していないか」しか調べる関数がありません。
しかし、「どれ位押し続けられたか」を調べたい事は必ず出てくると思います。
そこで、キーが押されているかどうかに、加え、「どれ位押し続けられたか」を判定する関数を作ってみます。
これはGetHitKeyStateAllという本家の関数を拡張した物です。
この関数を呼ぶ代わりにこれから実装するGetHitKeyStateAll_2を利用して下さい。
元の関数の仕様は是非チェックしておいて下さい。
int GetHitKeyStateAll( char *KeyStateBuf ) ;
http://homepage2.nifty.com/natupaji/DxLib/dxfunc.html#R5N28
このフォーマットに習ってGetHitKeyStateAll_2関数の仕様を書いてみますと・・。
|
サンプル エンターが押されたカウントを表示し、押された瞬間画面を白くする #include "DxLib.h" int GetHitKeyStateAll_2(int KeyStateBuf[]){ char GetHitKeyStateAll_Key[256]; GetHitKeyStateAll( GetHitKeyStateAll_Key ); for(int i=0;i<256;i++){ if(GetHitKeyStateAll_Key[i]==1) KeyStateBuf[i]++; else KeyStateBuf[i]=0; } return 0; } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ int Key[256]; ChangeWindowMode(TRUE);//ウィンドウモード if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化 while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){ //↑メッセージ処理 ↑画面をクリア ↑入力状態を保存 ↑ESCが押されていない DrawFormatString(0,0,GetColor(255,255,255),"%d",Key[KEY_INPUT_RETURN]);//エンター入力カウント if(Key[KEY_INPUT_RETURN]==1)//今の瞬間押されたら DrawBox(0,0,640,480,GetColor(255,255,255),TRUE);//画面全体を白い四角で表示 ScreenFlip(); } DxLib_End(); return 0; } |
41. 毎回書くプログラムの骨格(キー入力監視付)
上の40節でキーボード入力カウンタ関数を実装しましたが、ほとんどのゲームにおいてこれは必要になることでしょう。
ゲームを作るときは、まず、ここからスタートしましょう。
下のプログラムソースをコピペして、「//ココ!!」の部分に自分の好きなコードを追加して行って下さい。
これが骨格となります。
最初のうちはゲームを作るときは、下のコードは必ずかかないといけない最小限のコードだと思っておきましょう。
ゲームを作ったりサンプルを考えたりする時は毎回このコードをコピペして下さい。
#include "DxLib.h" int Key[256]; int GetHitKeyStateAll_2(int GetHitKeyStateAll_InputKey[]){ char GetHitKeyStateAll_Key[256]; GetHitKeyStateAll( GetHitKeyStateAll_Key ); for(int i=0;i<256;i++){ if(GetHitKeyStateAll_Key[i]==1) GetHitKeyStateAll_InputKey[i]++; else GetHitKeyStateAll_InputKey[i]=0; } return 0; } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode(TRUE);//ウィンドウモード if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化 while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){ //↑メッセージ処理 ↑画面をクリア ↑入力状態を保存 ↑ESCが押されていない //ココ!! ScreenFlip(); } DxLib_End(); return 0; }
〜中級編〜(中級的な関数の使い方)
51. 画像を回転、拡大縮小描写する。
画像の回転と拡大、描写を同時に扱える関数がDrawRotaGraphです。仕様詳細についてはこちらをご覧ください。
DrawRotaGraph( X座標 , Y座標 , 拡大率 , 回転角度 , 画像ハンドル , 透過フラグ
, 反転表示フラグ);
拡大率は1.0で等倍です。回転角度はラジアンで指定します。0度は0ラジアン、180度は3.14ラジアン、つまりπです。
360度は3.14*2ラジアンであり、0と同じです。3.14*2ラジアンと0ラジアンは同じですから、
360度ごとに最初に戻ります。つまりラジアンは増やしっぱなしでもくるくる回ってくれます。円周率は最初に
#define PI 3.1415926
このように定義しておきましょう。
初めて出てきた「反転表示フラグ」は画像を左右反転させて表示するかどうかのフラグでTRUEにすると反転、
FALSEにすると、反転せず普通に描画します。
#include "DxLib.h" #define PI 3.1415926 int image[16],count=0; char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ); LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ; while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){ DrawRotaGraph( 320 , 240 , (double)count*0.01 , PI*((double)count*0.001) , image[0] , TRUE ) ; count++; ScreenFlip(); } DxLib_End(); return 0; }
52. 特定の時間で特定の変化をさせる
ゲームは大抵1秒間に60回描写します。普段使っているループの最後にcount++;しているプログラムでは、
1秒間に、countが60増えます。これを利用して1秒間で、拡大率を0から1にしたいとき、どうしたらいいでしょうか。
拡大率とカウンタを対応させたいわけですね。
<pre>
拡大率 : 0.0→1.0
カウンタ: 0 →60
</pre>
これは、 (拡大率) / (カウンタ) で実現できます。つまり
(変化させたい量) / (その時間に増加するトータルカウンタ量)
で計算できます。 上記の例では、
カウンタに1/60をかけたらカウンタが0から60に増える間に拡大率は0から1に増加します。
---例---
4秒間で、画像を180度回転させたい時
--------
変化させたい量=回転角度3.14ラジアン
その時間に増加するトータルカウンタ量=60*4カウンタ
ですから、
count * (3.14/(60*4);
で、そのカウンタに応じて回転角が変化します。
実際にはint型と、double型が混合しないように注意してください。
以上の例の拡大率、回転角度を関数に表すとこうなります。
DrawRotaGraph( 320 , 240 , (double)count* 1.0/60.0 , (double)count * PI/
240.0 , image[0] , TRUE ) ;
#include "DxLib.h" #define PI 3.1415926 int image[16],count=0; char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ); LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ; while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){ DrawRotaGraph( 320 , 240 , (double)count* 1.0/60.0 , (double)count * PI/ 240.0 , image[0] , TRUE ) ; count++; ScreenFlip(); } DxLib_End(); return 0; }
53. 画像を回転、拡大縮小描写する。(中心座標指定)
51節で説明した画像の回転は、画像の中心を軸にして回転していました。しかし画像の回転軸を指定したい場合があります。
たとえば、時計の針なんかを描画したいときは、画像の中心が軸になってくれては困ります。
したがって、そのような時はDrawRotaGraph2関数を使用します。
詳しい関数の仕様はこちらでかくにんしてください。
int DrawRotaGraph2( x座標 , y座標 , 画像内の軸のx座標 , 画像内の軸のy座標 ,
拡大率 , 回転角度 , 画像ハンドル , 透過フラグ ,
反転表示フラグ ) ;
今回は、画像を表示するxy座標のほかに軸を指定するための座標も指定します。
といっても、よく意味が分からないでしょうから、例を見ながら覚えましょう。
例1
指定座標(320,240)に 画像内の(0,0)を軸に、45°回転させて(PI/4(rad))描画してみます。
このようになりました。赤い点が指定座標で、軸となっているのは画像内の(0,0)です。
軸に45°回転しています。
例2
してい座標(320,240)に 画像内の(50,100)を軸に、45°回転させて描画してみます。
先ほどの赤い点より上方に画像が表示されます。
画像内の(50,100)が指定座標となり、そこを軸に45°回転します。
例3
DrawRotaGraphと同じように画像の中心を軸に回転してみます。
画像内の(画像サイズx/2,画像サイズy/2)を軸に、回転させれば中央を軸に回転できます。
これらを踏まえてサンプルを見てみましょう。
(320,240)を指定座標に、画像の(50,150)を軸にしてくるくる回転させます。
#include "DxLib.h" #define PI 3.1415926 int image[16],count=0; char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ); LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ; while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){ DrawRotaGraph2 ( 320 , 240 , 50 , 150 , 1.0 , (double)count*0.01 , image[0] , TRUE ) ;//回転描画 DrawCircle ( 320 , 240 , 2 , GetColor(255,0,0) ) ;//中心点を描画 count++; ScreenFlip(); } DxLib_End(); return 0; }
54. 文字のサイズや太さを変更する(1度きりの場合)
DrawStringなどで描画する文字は毎回同じフォントでは都合が悪いときがありますから、自由にサイズや太さ、フォントの種類を
変更したい場合があります。今回は、自由にフォントサイズや、太さ、フォントの種類を変更して描画してみましょう。
以下で紹介している詳細の関数の仕様についてはこちら
SetFontSize(サイズ);
この関数で、フォントのサイズを変更します。引数はint型で、フォントのサイズは大体ピクセルと同じになります。
SetFontThickness(太さ);
この関数でフォントの太さを変更します。引数はint型で0〜9の値を指定します。
ChangeFont(フォントの種類);
この関数でフォントの種類を変更します。引数は文字列で指定します。
たとえば、MS明朝に変更したければ
ChangeFont( "MS 明朝" ) ;
と書きます。半角スペースや、全角英字など、忠実に書きましょう。ワードなどで調べると早いかも。
このフォントの種類は、パソコン本体に依存しますので、あまりマイナーなフォントの種類を使うと、
プレーする人のパソコンに入っていない場合がありますの、なるべくメジャーなフォントを使用しましょう。
ChangeFontType(フォントタイプ);
フォントを縁取ったり、キレイに描画したり、高速に描画できるフォントにしたりと、フォントのタイプを変更できます。
使用できるフォントタイプの引数については、こちらをご覧ください。
以上の事を利用してフォントを書いてみましょう。
#include "DxLib.h" #define PI 3.1415926 char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; DrawString(50,50,"DXライブラリ入門!",GetColor(255,255,255)); SetFontSize( 64 ) ; //サイズを64に変更 SetFontThickness( 8 ) ; //太さを8に変更 ChangeFont( "MS 明朝" ) ; //種類をMS明朝に変更 ChangeFontType( DX_FONTTYPE_ANTIALIASING ); //アンチエイリアスフォントに変更 DrawString(50,150,"DXライブラリ入門!",GetColor(255,255,255)); SetFontSize( 40 ) ; //サイズを20に変更 SetFontThickness( 1 ) ; //太さを1に変更 ChangeFont( "HGS創英角ポップ体" ) ; //HGS創英角ポップ体に変更 ChangeFontType( DX_FONTTYPE_ANTIALIASING_EDGE );//アンチエイリアス&エッジ付きフォントに変更 DrawString(50,300,"DXライブラリ入門!",GetColor(255,255,255)); WaitKey(); DxLib_End(); return 0; }
55. 文字のサイズや太さを変更する
文字のサイズを一発で変更するSetFontSizeなどの便利な関数は先ほど紹介しました。
しかし、SetFontSizeや文字の太さを変更するSetFontThickness関数などは、処理が非常に重く、
処理が返ってくるまでに時間がかかります。
なので、一度変更したキリそのフォントのみを使用する場合は上記関数で変更してよいのですが、
ループ中に何度か変更する場合、この関数は用いられません。
そこで、フォントについての情報をあれこれ指定して特定の「フォントデータハンドル」をつくり、
その作成したフォントデータハンドルにしたがって文字を描写すれば、様々な種類のフォントでも高速に描画できます。
ここではその、フォントデータを作成するCreateFontToHandleについて説明します。
今、フォントサイズ12で太さが3のエッジ付フォントがほしいとしますね。
しかし普段のフォントはサイズ10で、太さ1、エッジなしのフォントを使用するとします。
ここで、2種類のフォントデータハンドルを作成する必要は無く、最初にSetFontSizeなどでフォントデータを作成しておいけば
普通にDrawString関数などで描画すればそのフォントデータで出力されるため、
普段のフォントデータについては、最初に54節で説明した方法でフォントデータを設定し、
普通にDrawStringなどで描画します。
特別に文字の種類を変えたいときだけ、作成したフォントデータハンドルを使えばいいのです。
ではCreateFontToHandleについて説明します。この関数に持たせる引数は以下のとおりになっています。
CreateFontToHandle( フォントの種類 , サイズ , 太さ , タイプ ) ;
各持たせる引数の種類は54節で説明したものと同じなので、それらを参考にしてください。
この関数を呼ぶと、返り値にint型のハンドルが返ってきますから、そのハンドルを変数に保存します。
たとえば、種類をMS明朝、サイズを22、太さを2、タイプをDX_FONTTYPE_NORMALにしたとすると、
int FontHandle;
FontHandle = CreateFontToHandle("MS 明朝" , 22 , 2 , DX_FONTTYPE_NORMAL);
とすることで、FontHandleに作成したフォントデータのハンドルが格納されます。
描画するときは、このフォントデータハンドルを指定しましょう。
この変数をいくつも用意し、多数のハンドルを作成する事で、たくさんの種類のフォントデータで文字を描画することが出来ます。
作成したフォントデータで文字列を描画する時はDrawStringToHandleで描画します。
DrawStringToHandle( x座標 , y座標 , 描画文字列 , 色 , フォントデータハンドル
) ;
色はGetColor(R,G,B);で指定するものです。書式付で描画する時は
DrawFormatStringToHandle( x座標 , y座標 , 色 , フォントデータハンドル ,
文字列 , データ........ ) ;
を使用しましょう。
#include "DxLib.h" int FontHandle; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; FontHandle = CreateFontToHandle("MS 明朝" , 22 , 2 , DX_FONTTYPE_NORMAL); DrawString(50,50,"DXライブラリ入門!",GetColor(255,255,255)); DrawFormatStringToHandle( 50 , 150 , GetColor(255,255,255) , FontHandle , "第%d節" , 51+4 ) ; WaitKey(); DxLib_End(); return 0; }
〜サンプルプログラム編〜
s0. 「たった今入力された」を判定する
汎用的に使えるよう関数として40節に新しく作りました。
キーがどれ位押されたかを監視する関数
をご覧下さい。
s1. 関数ポインタ基本
シューティングゲーム等は、敵の攻撃によって様々な関数で計算して玉を発射します。
その時間や状況によって使う関数が変わるのです。すなわち大量の関数名が出てきますね。
if(1回目の攻撃なら)
ShotType1();
if(2回目の攻撃なら)
ShotType2();
if(3回目の攻撃なら)
ShotType3();
if(4回目の攻撃なら)
ShotType4();
と書いてもいいですけど、なんだかちょっと賢くない書きかたのような気がしますね。
これをあらかじめ、「関数のポインタを配列でまとめて」おいて、配列要素番号を変更する事で
どの関数に処理を渡すか決定できれば随分スムーズにプログラムが書けるんじゃないでしょうか。
if(攻撃モード変更したら)
i++;
ShotType[i]();
こんな風に書けば随分スマートですね。
では関数をポインタを使って配列にまとめてみるプログラムをかいて見ましょう。
サンプルプログラムでは
ShotType1();
ShotType2();
ShotType3();
という関数をShotFunctionで纏めています。
以後、ShotFunctionを使用する事で、任意のショットタイプ関数へ処理を渡す事が出来ます。
#include "DxLib.h" int c=0,White,i=0; void ShotType1( void ) ; // 敵のショットパターン1関数 void ShotType2( void ) ; // 敵のショットパターン2関数 void ShotType3( void ) ; // 敵のショットパターン3関数 void (*ShotFunction[])( void ) = { ShotType2, //この順番で配列に格納される ShotType1, //[0],[1],[2]の順番で使用すると ShotType3, //2,1,3の順番で処理される。 } ; void ShotType1( void ){ DrawString(200,200,"タイプ1ショット発射中。",White); } void ShotType2( void ){ DrawString(200,200,"タイプ2ショット発射中。",White); } void ShotType3( void ){ DrawString(200,200,"タイプ3ショット発射中。",White); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode( TRUE ) ; //ウィンドウモードに変更 if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 White = GetColor( 255 , 255 , 255 ) ; //色の取得 SetDrawScreen( DX_SCREEN_BACK ) ; // 描画先を裏画面に設定 while( 1 ){ ClearDrawScreen(); // 裏画面のデータを全て削除 if (c==0) i=0; else if(c==100) i=1; else if(c==200) i=2; else if(c==300) break; ShotFunction[i](); // 各関数へ処理を渡す c++; ScreenFlip() ; // 裏画面データを表画面へ反映 } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
実行結果
s2. サウンドノベル風文字列表示法1
ノベルゲームやアドベンチャーゲーム、同人ゲームなどのセリフの表示は、目で追いやすいように、時間差で
表示されていますね。あれを作ってみましょう。まずは、簡単なプログラムから行きます。
あらかじめ、表示する文章を作っておき、実際に画面に表示するための文字列を格納する変数を別につくり、
時間差で、あらかじめ作ってある文字列から、現在文字列を表示する変数に順々にコピーしながら表示すると
よいでしょう。
今回は、s0節で説明した、「たった今入力された」を判定する で紹介したプログラムを使って、エンターを押したかどうか判定します。
時間の測定にはcounter変数を用いています。処理が1周するたびに、counterの値を増やします。
counterの値が5の倍数の時に、処理を進めることで、5周に1度処理をすることが出来ます。
サンプルプログラムでは
i が「文章の各文字までのバイト数」を表します。
つまり i が4なら4バイト目まで、全角2文字までを格納することになります。
i が4の時、最初の4バイト、全角2文字を格納し、[4]='\0'する事で、その配列を文字列として表示できます。
このiを2ずつ増やしていくことで、全角1文字ずつ表示する文字列を増やすことが出来ます。
ch_iは各文章の番号です。ch_iが1つ進むと、次の文章になります。
宣言がch[5][80]で宣言されています。実際に扱うとき、ch[ch_i][i]で扱っていくことになります。
dispは、その現在表示すべき文字列のコピー先配列です。
chは全ての文字列のデータです。
#include "DxLib.h" int i=0,j,ch_i=0,counter=0,White; char Key[256],oldKey[256]={},disp[80]="",ch[5][80]={ "プログラムを習得するには、実際に自分で書いてみることが大事。", "参考書を読むことも重要だが、数学のように演習を通して、", "体に身につけるようにしよう。", "きっと、よりはっきりと、関数の使い方、", "プログラムの書き方がわかるだろう。" }; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ) ; White=GetColor(255,255,255); while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ for(j=0;j<256;j++){ if(oldKey[j]==0 && Key[j]==1) Key[j]=2; oldKey[j]=Key[j]; } if(Key[KEY_INPUT_RETURN]==2){ // 前回押されてなくて、今回押されていたら i=0; // iを初期値に戻し ch_i++; // 表示する配列を1つずらす if(ch_i==5) // これ以上表示する配列がなくなったらbreak; break; } // 配列の終端-2以下で、今回コピーするiまでの配列要素のi番目が\0じゃなくカウンターが5の倍数なら if(i<=80-2 && ch[ch_i][i]!='\0' && counter%5==0){ i+=2; // 全角文字なので配列要素2つずつカウントアップ strncpy(disp,ch[ch_i],i);// iバイトまでの配列要素をdisp配列にコピー disp[i]='\0'; // 終端記号を代入 } DrawFormatString(10,420,White,"%s",disp);//disp配列の文字データを表示 counter++; ScreenFlip() ; } DxLib_End() ; return 0 ; }
実行画面
s5. 処理速度を一定にする。
コンピュータは1秒間に何百万回も計算するのに、現在書いているコードを実行するとループは1秒間に60〜120位しか
ループしてない事に疑問を持った方もいらっしゃるでしょう。
ScreenFlip() ; この関数は画面のリフレッシュレートにあわせて裏画面を反映する関数です。
リフレッシュレートとは、モニタが1秒間に画面を書き換える回数です。
普通モニタは1秒間に60〜120回描画しています。モニタのリフレッシュレートにあわせて
裏画面を反映させないとチラチラして表示されてしまうことは想像できますね。
ですから、 ScreenFlip() ; を書くと、ループの回数がリフレッシュレートと同じになるのです。
(ただし、処理が重く、1回のループに1/60〜1/120秒以上かかっていたらリフレッシュレートと同じにはなりません)
リフレッシュレートは自由に変更できます。
デスクトップで「右クリック」>「プロパティ」>「設定」タブ>「詳細設定」>「モニタ」で変更できます。
普通モニタは60ヘルツつまり1秒間に60回更新します。良質なモニタなら120以上のリフレッシュレートも存在します。
ここで、キャラクターを動作させるのに今までcounterを使っていました。
int counter=0;
while(......){
counter++;
}
こんな風にcounterの数字によってキャラを移動させたりしていましたが、モニタのリフレッシュレートが早ければ
それだけ早くキャラが動いてしまいます。
ゲームをしてもらう人に「リフレッシュレートを60に設定してから遊んでください」というのはあまり実用的ではありません。
ですからこちらでゲームループ回数をのリフレッシュレート60に合わせましょう。
120ヘルツであっても100ヘルツであっても、処理が1回に1/60秒かかるまで待たせるのです。
1÷60=0.166666......
つまり、1回の処理に16(ms←ミリ秒)〜17(ms)かかればリフレッシュレート60に合います。
1回の処理にかかった時間を計測し、16ミリ秒より遅ければトータル16ミリ秒になるまでSleepするようにプログラムを書いてみます。
まず、この処理を担当する関数名をwait_fancとでも名づけましょう。
void wait_fanc(){
ローカル変数を宣言し、変数のデータが関数を抜けても失わせれないようstatic変数で宣言します。
static int t=0;
tには1回前の時間を格納します。GetNowCountで現在の時間が取得できますから
現在の時間 - 1回前の時間 を計測すれば1回の処理にかかった時間がわかります。
これをほかで用意することにしますint型変数waitに格納します。
wait = GetNowCount()-t;
t=GetNowCount();
waitがもし16ミリ秒より少なければ、トータル16ミリ秒になるまでSleepさせます。
if(16-wait>0)
Sleep(16-wait);
こうすることで、1回の処理が16ミリ秒に統一できました。
1000 ÷ 16 = 62.5
ですからだいたい1秒間に60フレームでゲームが作成できます。まとめると以下のようになります。
void wait_fanc(){ int term; static int t=0; term = GetNowCount()-t; if(16-term>0) Sleep(16-term); t=GetNowCount(); return; }
今回は次の節と一緒にサンプルプログラムを作っていますので、今回はサンプルプログラムがありません。
次の節を一緒に読んでください。
s6. FPSを表示する。
FPSとはFlame per secondの略で、1秒間に何フレームかを表すものです。
先ほど5節で1秒間に60フレームに統一する関数を作りましたね。
では、実際に現在秒間何フレームで動作しているか表示する関数を作って見ましょう。
FPSを表示する方法として以下の方法をとります。
1回の処理にかかった時間を60回計測します。
60回の計測値の平均を出します。1000を1回の処理の平均時間で割ります。
すると良質なFPSが計算できますね。平均を取らずに毎回FPSを計算すると、
表示数値がチラチラしてしまうので、時間をおいてFPSの表示数値を変更する必要があります。
では実際に関数を作っていきましょう。
とりあえずFPSを表示する関数をvoid fps(){とでも書いておきます。
ループカウンタ用の i と、1回の処理時間の平均を格納するave, そして60回分の計測データを格納するf[60]を用意します。
int i;
static int ave=0,f[60];
後者2つは関数を抜けたときにデータが失われないようstatic変数で宣言しておきましょう。
waitは先ほどの関数で格納しました。1回の処理にかかった時間です。
f[count%60]=wait;
countは1回の処理で1増えるカウンタです。このように書くことでcountの値が60を超えても
何度も0から59まで順番に指定することが出来ます。配列にwaitを格納していきましょう。
何度もfps関数がよばれ、60回目、つまりcount%60==59になったら平均をだしましょう。
if(count%60==59){
ave=0;
for(i=0;i<60;i++)
ave+=f[i];
ave/=60;
}
そして、1000から平均でわってやってFPSを出しましょう。%.1fという書き方は、小数点第1位まで表示するという書き方です。
DrawFormatString(0,0,GetColor(255,255,255),"%.1f",1000.0/(double)ave);
ここで注意してください!!aveが0の時は割ってはいけません。
数学の常識で数値は0で割ってはいけないというお約束がありますね?
「そんなの知らない」「知ってるけど、なんで0で割っちゃだめなの?」と疑問に思った方、遠慮なく掲示板ででも聞いてください。
とりあえず皆さん習っているはずです。sin cos tanの表にもタンジェントが90°の時「値無し」となっているはずですよ。
まぁそれはおいておいて、ですからif(ave != 0)を必ず書いておきましょう。計算出来ないのでバグがおきてしまいます。
これらのことをまとめるとこうなります。
void fps(){ int i; static int t=0,ave=0,f[60]; f[count%60]=GetNowCount()-t; t=GetNowCount(); if(count%60==59){ ave=0; for(i=0;i<60;i++) ave+=f[i]; ave/=60; } if(ave!=0){ DrawFormatString(0, 0,GetColor(255,255,255),"%.1fFPS",1000.0/(double)ave); DrawFormatString(0,20,GetColor(255,255,255),"%dms" ,ave); } return; }
全てのことをまとめて書くと以下のサンプルプログラムのようになります。
以下のサンプルプログラムは上で紹介した関数をそのまま貼り付けて、メインから呼んでいるだけです。
#include "DxLib.h" int count=0; char Key[256]; void wait_fanc(){ int term; static int t=0; term = GetNowCount()-t; if(16-term>0) Sleep(16-term); t=GetNowCount(); return; } void fps(){ int i; static int t=0,ave=0,f[60]; f[count%60]=GetNowCount()-t; t=GetNowCount(); if(count%60==59){ ave=0; for(i=0;i<60;i++) ave+=f[i]; ave/=60; } if(ave!=0){ DrawFormatString(0, 0,GetColor(255,255,255),"%.1fFPS",1000.0/(double)ave); DrawFormatString(0,20,GetColor(255,255,255),"%dms" ,ave); } return; } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ); while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){ fps(); count++; ScreenFlip(); wait_fanc(); } DxLib_End(); return 0; }
実行画面
s7. メーターを表示する。
ゲームを作ってると敵や味方のHPを表示する必要があると思います。
しかし決まったスペースに現在のHPとHPの最大値にあったメーターを
表示するのは案外パッと作るには難しいものです。
サンプルでは横幅を定義WDに設定した幅に、hpとhp_maxにあった割合でメーターを表示します。
#include "DxLib.h" #define WD 400 int hp=200 , hp_max=200; char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //初期化処理 SetDrawScreen( DX_SCREEN_BACK ); //描画先を裏画面に設定 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 DrawBox(100,100,100+WD ,120,GetColor(0,255,255),FALSE);//メーターの枠を描画 DrawBox(100,100,100+WD*hp/hp_max,120,GetColor(0,255,255),TRUE );//メーターの中身を描画 if(hp>0) hp--; ScreenFlip(); } DxLib_End(); return 0; }
実行画面
s11. 当たり判定。
シューティングゲームやアクションゲームなど、自機と敵との当たり判定を計算したい時がありますね。
そんな時はピタゴラスの定理を使いましょう。
直角三角形の底辺の2乗と高さの2乗の和は斜辺の2乗に等しいという奴です。
斜辺^2 = x^2 + y^2
これをふまえて図を見てください。
青を敵の当たり判定範囲だとします。黄色を自機の当たり判定範囲だとします。
int x , y , range ; として変数を用意し、
range = 自機範囲 + 敵範囲;
x = 自機.x - 敵.x;
y = 自機.y - 敵.y;
とすれば、
x^2 + y^2 = range^2
が成り立ちます。つまり、これより近くにあると言う事は、
x^2 + y^2 < range^2
この時、接触している事がわかります。その事を見せたサンプルがこちらです。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" typedef struct{ int x,y,range; }enemy_t; enemy_t enemy[2]; int count=0; char Key[256]; void ini(){ enemy[0].range=100; enemy[1].range=180; } void set(){ enemy[0].x=GetRand(640); enemy[0].y=GetRand(480); enemy[1].x=GetRand(640); enemy[1].y=GetRand(480); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ini(); int x,y,range=enemy[0].range+enemy[1].range; if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //初期化処理 SetDrawScreen( DX_SCREEN_BACK ); //描画先を裏画面に設定 SetDrawBlendMode( DX_BLENDMODE_ADD , 256 ) ;//これは気にしなくていい。 while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){ //↑メッセージ処理 ↑画面をクリア ↑キーボード入力状態取得 ↑ESCが押されると終了 if(count%120==0) set(); DrawCircle(enemy[0].x,enemy[0].y,enemy[0].range,GetColor(255, 0, 0),TRUE); DrawCircle(enemy[1].x,enemy[1].y,enemy[1].range,GetColor( 0,255,255),TRUE); x=enemy[0].x-enemy[1].x; y=enemy[0].y-enemy[1].y; if(x*x+y*y<range*range) DrawString( 0,0,"○接触しています!!○",GetColor(255,255,0)); else DrawString(300,0,"×接触していません。×",GetColor(255,255,0)); count++; ScreenFlip(); } DxLib_End(); return 0; }
実行結果
s12. メッセージボックスのウィンドウを利用する
ウィンドウズの機能でサクッと簡単に使えるウィンドウにメッセージボックスがあります。
下のサンプルプログラムを実行するとこんなウィンドウが出てきます。
ゲーム画面は全画面じゃなく、ウィンドウ画面で利用したい時もあります。それをユーザーが任意に選べるように
したい場合がありますね。でも、途中でウィンドウを変更してしまうとそれまでの画像データなどが全部初期化されてしまうので、
出来れば最初に選ばせたいですね。ですから、
DxLib_Init()を呼ぶ前にこのメッセージボックスによってユーザーの意思を受け取り、処理をさせます。
まず、以下のプログラムをコピペして実行してみてください。
↓コピー&コンパイル用サンプルプログラム↓
#include "DxLib.h" void message_box(){ int flag; flag=MessageBox( NULL , TEXT("フルスクリーンモードで起動しますか?") , TEXT("スクリーン設定") , MB_YESNO | MB_ICONQUESTION ); if(flag==IDNO) ChangeWindowMode( TRUE ); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ message_box(); if(DxLib_Init() == -1 ) return -1; //初期化処理 WaitKey(); DxLib_End(); return 0; }
実行結果
省略
MessageBoxの関数は
第一引数:NULLのままにしておきます。
第二引数:ウィンドウのメッセージ
第三引数:ウィンドウのタイトル
第四引数:ウィンドウの種類
を指定して使用します。例えば今回のウィンドウは「はい・いいえ」で答える形式のものなので
MB_YESNO
を第四引数にしていしました。?のクエッションマークはMB_ICONQUESTIONを渡す事で表示されます。
これらの引数の詳細はググってみてください。
http://www.google.co.jp/search?hl=ja&q=messagebox&lr=
〜物理入門〜
ジャンプ動作やボールの跳ね返りなどを忠実に表現したいのに物理を知らないからプログラムがかけない・・・。
サンプルプログラムを見ても、基本的な物理の知識がないから何かいてあるのかわからない・・・。
そんな人の為に作りました「物理入門」。ここでは難しい話や億劫になるまじめなお勉強はいたしません!
ゲームに必要な最低限の事を実際にゲームプログラムと交えながらわかりやすく解説しています。
文型の人でも、数学・物理が嫌いな人でも抵抗無く読んでいただけるよう作ったつもりなので是非今まで
苦手意識を持っていた方でも肩の力を抜いて読んでください。
p1. 物体の速さとモニタのピクセル位置
--------どうでもよい前置き----------
小学校で 距離=速さ×時間 という方程式を習ったと思います。
速さというのはある時間で進む距離の事です。速さには単位があります。
自動車でよく「60キロ」「100キロ」と言っているのは「60(km/h)」「100(km/h)」の事です。
(km/h)は「キロメートル マイ ジ」と読みます。
kmはキロメートル、距離の単位の事です。
hはhourつまり1時間の事です。ジといっているのは1時間だからです。
単位をよく見てください。( km / h ) よく見ると距離を時間で割っていますね。
距離を時間で割っている・・・そうです「距離=速さ×時間」の式の両辺を時間でわってやると
「距離 / 時間 = 速さ」となっています。つまり単位を見ただけでこれが速さを表していることだとわかりますね。
キロメートルを時間(hour)で割ったもの。つまりこれが表す速さは
「1時間で進む距離」の事です。
一方 ( m / s )という単位があります。「メートル マイ ビョウ」と読みます。
1秒間に進む距離(メートル)です。物理では普段使っているキロメートルマイジではなくこちらの
メートルマイビョウ(m/s)を使います。
物理で「速さ」を表すときはたいてい(m/s)であり、速さは1秒間に進む距離だと覚えて置いてください。
--------------絶対覚えておく要点-----------------
速さは1秒間に進む距離(メートル)
----------------------------------------------------
これがわかればこの節OK!
問題1 |
速さ3(m/s)で10秒走りました。距離は何メートルでしょう? |
答え |
30(m) |
↑答えは「答え」の下部分を範囲指定してチェック!
問題2 |
速さ5(m/s)でt秒進みました。現在距離は何メートルでしょう? |
答え |
5*t (m) |
↑答えは「答え」の下部分を範囲指定してチェック!
問題3 |
速さ2(m/s)でt秒進みました。1メートルはモニタで言う50ピクセルです。現在モニタでは何ピクセル進んだでしょう? |
答え |
2*t*50(pixel) |
↑答えは「答え」の下部分を範囲指定してチェック!
問題3まで正解した人はOKです!
なんとなく今まで分かっていた人も問題3で「お?」と思ったと思います。
そうです、いくら計算できても、モニタでいうとどれ位進んだのかわからなければモニタで表現できませんね。
では問題3を実際にプログラムで書いて見ましょう。
速さは「v」で表します。
時間は「s」で表します。(1秒=secondの事です)
水平距離は「x」で表します。(水平方向とは横方向の事、x軸方向の事です)
モニタのキャラのx座標はch_xとしておきましょう。
速さは不変ですから定義しておきます。
#define v 2.0
oldtは計測開始時点の時間を格納します。
int oldt=0;
if(oldt==0)
oldt=GetNowCount();
tには計測開始時間からの経過時間を格納します。
t = (double)(GetNowCount() - oldt)/1000.0;
距離を求めます。
x = v * t ; //距離 = 速さ * 時間
計算した距離をモニタのピクセルサイズに合わせます。
ch_x = (int)(x * 50.0);//1mは50pix
DrawRotaGraph( ch_x , 240 , 3.15 , 0.0 , image[count%60/20+6] , TRUE )
;
拡大率3.15倍(この説明はした)で描画します。image[count%60/20+6]というのは、
まず、imgae[6] , [7] , [8]が右向きの画像であることと
リフレッシュレートが60だとして、countを60で割ったあまりをだせば1秒間で1通りの動作をさせる。
60を20で割れば3通りの数値が出てくる。
ということから計算した式です。
---------ここわからなくてイイ----------
画像のキャラの身長が26ピクセルですから「1メートルはモニタでいう50ピクセル」を実現するためには
このキャラクタの身長が実際にリアル世界にいると170cm=1.7mだとすると
50 : 1 = ? : 1.7
(100ピクセルで1メートルなら何ピクセルで1.7メートル?)
を計算すると、?は85ピクセルであることがわかります。キャラを85ピクセルで描けば1.7メートルのキャラが
立っている世界が作れます。27ピクセルのキャラを85ピクセルに拡大するためには85/27
≒ 3.15
つまり3.15倍拡大して表示すればいいことが分かります。
ですからDrawRotaGraph関数で拡大して表示しましょう。
---------------ココマデ--------------
全てのことをまとめて書くと以下のサンプルプログラムのようになります。
以下のサンプルプログラムは上で紹介した物をそのまま貼り付けただけです。
#include "DxLib.h" #define v 2.0 int image[16],count=0,oldt=0,ch_x; double t,x; char Key[256]; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ); LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image ) ; while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){ if(oldt==0) oldt=GetNowCount();//計測開始時間を格納 t = (double)(GetNowCount() - oldt)/1000.0;//計測を始めてからの時間 x = v * t ; //距離 = 速さ * 時間 ch_x = (int)(x * 50.0);//1mは100pix DrawRotaGraph( ch_x , 240 , 3.15 , 0.0 , image[count%60/20+6] , TRUE ) ;//6.3倍で描画 count++; ScreenFlip(); } DxLib_End(); return 0; }
実行画面
どうでしょう、テクテクあるいているシュミレーションが出来たと思います。
お使いのモニタのリフレッシュレートの設定(処理速度を一定にする。をお読み下さい)にかかわらず、移動速度は一定になったはずです。
ご自分で1秒間に2mの速さで歩いてみてください。少々早歩きだと思います。
実際にシュミレーションしたような速さになったのではないでしょうか。
t1. セーブデータの作り方 (簡易暗号化編)
セーブデータを作る時、パッと思いつく方法はテキスト形式のファイルに数字を保存する方法でしょう。
例えば
int hp=500;
となっているhpのデータは
FILE *fp; ⇒書き込み形式で開く。
fprintf(fp , "%d", hp);
で指定ファイルにデータが書き込めます。
ファイルの書き込みの仕方がわからないときはfopenで調べましょう。
しかしテキスト形式でただデータを書いただけではユーザーにファイルを開いて改竄させられてしまう危険性もあり、
このままではよろしくありません。なのでココで簡単な暗号化を勉強しましょう。
まず、暗号化というのはどのようなことでしょうか。
今「500」というデータをファイルに作りたいと思います。
この時、500というデータを特定の鍵となるキー数値を利用して違う値に変換してみます。
暗号化というものは、要は「鍵となるキー数値」を利用して違う値に変換した数値で保存し、
キー数値を利用して元に値に戻す事です。
例えば思いっきり単純なたとえ話をします。
500というデータを保存したい時、キー数値を120とします。
500 + 120 = 620として620という新たな数値を作り出して保存します。
ロードする時はこの620という数値を取り出して
620 - 120 = 500として元の500を取り出します。
このキー数値や計算アルゴリズムを複雑化することで、質の高い暗号化を目指します。
まずこの節では簡単な暗号化をやってみます。
排他的論理和、という言葉を聞いた事はあるでしょうか。
http://www.asahi-net.or.jp/~AX2S-KMTN/ref/logicope.html
ここにとてもわかりやすく書いてあるので、知らない人は参考にしてください。
暗号化はこの「排他的論理和」を使って作ってみます。
データをAという定数で排他的論理和をとり、もう一度Aという定数で排他的論理和を取ると元の数に戻ります。
これを利用します。排他的論理はC言語では「^」と書きます。以下のサンプルを見て、実行結果を予測してください。
#include <stdio.h> void main(void){ int key = 0x45af6e5d; //任意の暗号化キー int data = 500; printf("%d\n", data); data = data^key; printf("%d\n", data); data = data^key; printf("%d\n", data); } 実行結果 500
1169125289
500
予測した実行結果と同じものになっていたでしょうか。2つ目に何やらわけのわからない数字が出来ていますが、
これが暗号化キーを用いて排他的論理和を出した数値です。このデータをファイルに記憶させ、
読み込むときはこのデータをもう一度暗号化キーを使って演算子、戻してやればいいのです。
論理和
セーブしたいデータ⇒ | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | |
暗号化キー | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | |
暗号化されたデータ | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
状態 | 全て1 | 変わらず |
論理積
セーブしたいデータ⇒ | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | |
暗号化キー | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | ↓ | |
暗号化されたデータ | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
状態 | 変わらず | 全て0 |
セーブしたいデータのそれぞれの論理積と論理和の結果は表のようになります。
それぞれのピンクの部分は変更されていない事がわかりますね。つまり複合化する時は、このピンクの部分を取り出せばいいのです。
また、論理和の緑の部分は1以外だと、論理積の緑の部分は0以外だと改竄されたものだという事がわかります。
このようにして少しまともにセーブデータが作れます。
暗号化については複雑にすればいくらでも良質なデータが作れます。
シェアウェアなどでデータを配布する時はこの方法ではまだ不十分でしょう。
もし奥深く追及したい人はまた別途暗号化について、勉強してください。
t3. 画像・音声ファイルを独自の拡張子に
画像や音声のファイルは実行ファイルと一緒に配布する必要がありますが、そのままの拡張子ではユーザーに勝手に開かれてしまいます。
その素材が2次配布禁止のものだったら勝手に開かれては困りますし、フリーだとしても、
ラストの画面など、苦労しないと見られない画像を最初から見られてしまうのは嫌ですね。
そこで簡単に出来る方法として、拡張子を変える方法です。
拡張子とは、「data1.jpg」の「.jpg」の部分です。これはファイルの種類を示しています。もし拡張子が表示されていない時は
XPの場合、
マイコンピュータ>ツール>フォルダオプション>表示>詳細設定
「登録されている拡張子は表示しない」
にチェックが入っているので外しましょう。
実はこの拡張子を変更しても、ファイル自体のデータはなんら変わりません。
関連付けが取れるだけなので、ダブルクリックでデータが開けないだけです。
だから.jpgを.datとかに変更してダブルクリックで開けなくなっても、画像データなのならペイントにドロップすると開けます。
プログラムで読み込むときも同様で、なんら関係なく開けます。
しかし、ダブルクリックで開けないファイルはユーザーは普通開こうとしないでしょう。
そこでファイルを全部.datとか.bomとかダブルクリックでは開けない拡張子にしてしまいます。
どうでしょう、安易に開こうとは思わない感じになりましたね。
しかし、何とかして開こうとするユーザーには開けてしまいますから、これだと不十分だと思う人は
DXライブラリの機能であるアーカイブ機能をりようしてください。
これについては本家で詳しく説明されています。
アーカイブ機能の説明へ
いいやこれもバイナリファイルで端から見ていけば取り出せるという人はほっといてください。
t4. 実行ファイルをオリジナルアイコンに
実行ファイルは大抵、ウィンドウのアイコンになっていると思います。
例えば私の作ったゲーム「BLACK」で、アイコンを付けていないとこんな感じになります。BLACK.exeを実行するとゲーム開始です。
なんとなく味気ないですね。実行ファイルにはその絵をみただけでどんなゲームか連想できるようなアイコンを付けたいものです。
そこでペイントやフォトショップなどの画像ソフトを使って書いたアイコンを関連付けて見ます。
するとこのように変わります。
どうでしょう。ゲームっぽくなって、しかもみただけでどんなゲームかわかりそうですね。
これは本家DXライブラリのHPに詳しく説明が書かれているので、そちらをご覧下さい。
オリジナルのアイコンを付ける(本家DXライブラリ)
t5. 実行ファイルから盗まれるデータ
実行ファイルを作ると、test.exeなどの拡張子がexeの実行ファイルが生成されますね。
一見、このファイルは開く事出来ないし、テキストエディタで無理やり開いても意味不明な文字が並ぶだけで、
何も読み取る事が出来ないと思いますよね。しかし、「バイナリエディタ」なるもので開くと実行ファイルの情報が読み取れてしまいます。
実際に試してみましょう。
#include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ char st1[64]="見られてはいけないデータ1"; char st2[64]="見られてはいけないデータ2"; char st3[64]="見られてはいけないデータ3"; DxLib_End(); return 0; }
このようなソースコードを書いて、出来た実行ファイルをバイナリエディタで開いてみます。
コンパイルしている場所からユーザー名がわかってしまったり・・・
表示されるはずのない、ユーザーに見られてはいけないデータなどが・・・
見られてしまうんです。ですから、配布する実行ファイルを作る時には、コンパイル場所に注意し、
見られては困る文字列は、各文字コードを+1したデータを外部データとして入力させるなど、
読み取られてはいけないデータは自分で守る必要があります。
上記で使用したバイナリエディタはこちらのソフトです。
利用規約
ゲームプログラミングの館に掲載しているソースコードは自由にご自分のゲームなどに引用して下さい。
また、ご自分のHPなどに常識の範囲内で二次利用していただいて構いません。
(常識の範囲外=例えばここのデータを完全コピーして他のサーバーで勝手にアップロードし、自分のHPであるかのように装う等)
他の掲示板や自サイトにソースコードをコピーしていただいて構いませんが、
その際はゲームプログラミングの館のコードである事をお書き下さい。
ゲームプログラミングの館で紹介しているソースコードや理論を使って起きた問題や損失などには
一切責任を持ちませんので、自己責任でご利用下さい。
DXライブラリ著作権表示
DX Library Copyright (C) 2001-2006 Takumi Yamada.