<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>TAKABO SOFT&#187; カテゴリー「DirectXゲーム作成講座　ゲーム制作基礎編」｜TAKABO SOFT</title>
	<atom:link href="http://takabosoft.com/category/directx-game/feed" rel="self" type="application/rss+xml" />
	<link>http://takabosoft.com</link>
	<description>ドット絵エディタ「EDGE2」、MIDI音楽編集ソフト「Domino」、楽曲などを配布している個人サイトです。</description>
	<lastBuildDate>Wed, 04 Apr 2012 14:30:01 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>第11回　自機の方向へ弾を撃つ&amp;アニメーション基礎</title>
		<link>http://takabosoft.com/19991231153955.html</link>
		<comments>http://takabosoft.com/19991231153955.html#comments</comments>
		<pubDate>Fri, 31 Dec 1999 06:39:55 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991231153955.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_11.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_11.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_11.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、どこからか弾が沸き、自機（スライム）の方向へ飛んできますよね？</p>
<p>というわけで、今回は角度の算出とアニメーションについて説明していきたいと思います。</p>
<h3>三角関数のお勉強 その1</h3>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/sankaku0.gif" align=left><br />
第4回では、三角関数としてsin、cos、tanを説明しました。<br />
しかし、これだけで、敵から、自機への角度を算出する事は出来ません。</p>
<p>そこで、今回はatan（アークタンジェント）というものを使用します。</p>
<p>普通のtanは</p>
<p>  tanΘ=b/a</p>
<p>でしたが、アークタンジェントは</p>
<p>  Θ=atan（b/a）</p>
<p>となります。要するに、タンジェントの逆算みたいな感じですね。</p>
<h3>三角関数のお勉強 その2</h3>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/sankaku2.gif" align=left><br />
というわけで、基本が解れば、あとは当てはめるだけですね。<br />
左の図のように敵、自機が存在し、角度Θを求めたい場合、</p>
<p>  Θ=atan（（sy-my）/（sx-mx））</p>
<p>とすればいいですよね？</p>
<p>ただし、y/xが例えば1/1、-1/-1の場合、値は一緒ですよね？<br />
という事で、atanの返値Θも半分の180度周期になってしまいます。（ちなみにsin,cosは360度周期,sin(0)=sin(360)=sin(360*n)）</p>
<p>そこで、x,yの符号を利用して、ある条件の時はΘに180を足すと言った処理が必要になってきます。</p>
<p>また、xが0の場合、y/0=∞となってしまい、エラーとなってしまいます。<br />
これも回避しなければなりません。</p>
<h3>アークタンジェントをC言語で使用する</h3>
<p>C言語でアークタンジェントを使用したい場合は、「math.h」をインクルードした後、atan( double v ) を呼び出せば出来ます。</p>
<p>ただ、上でも書いたように、返ってくる角度Θは180度周期だったり、xが0だった場合にエラーとなったりします。<br />
で、いろいろ判定して正しい値を出そうとしがちですが、C言語にはatan2( double y, double x) という関数が用意されています。<br />
この、yとxに値を引き渡してやると、360度周期で角度を返してくれます。しかも、xが0の時の処理もしっかりやってくれるので便利です。</p>
<p>ただ、atanもそうですが、atan2の返値の単位はラジアンです（0～360度=0～2π）。<br />
atan2の場合は-π～+πまでが返ってくるので、atan2の返値をπで割れば、-1～+1までになりますよね？<br />
そしてπは180°ですから、その値に180を掛けてやると、値は-180°～+180°になりますね？<br />
これに、360°を足し、360で割った余りを出すと、0～359°の値が出てきます。</p>
<pre class="brush: cpp;">
	double tmp;
	short angle;
	tmp=atan2(-double(sy-my),double(sx-mx));
	tmp/=3.1415926535;
	tmp*=180;
	tmp+=360;
	angle=(short)tmp%360;
</pre>
<p>こんな感じかな。</p>
<p>これで敵から自機への角度Θが出せますね？</p>
<h3>サンプルコードの解説</h3>
<p>今回のコードは、「第9回 キー入力によって弾を発射」を少し改良し、ランダムに弾を作成し、自機の方向へ飛ばすようにします。<br />
弾の移動等、詳しい事は第9回を参照してください。</p>
<p>今回は、ついでに弾をアニメーションさせる事にするので、_bullet構造体にanimationという変数を<br />
追加しておきます。</p>
<pre class="brush: cpp;">
//弾用変数
struct _bullet{
	BOOL enable;	//使用:1  未使用:0
	float x,y;		//座標
	char animation;	//アニメーション
	short angle;	//進む角度
}bullet[BMAX];
</pre>
<p>では、メインループを見てみます。</p>
<pre class="brush: cpp;">
//----------[ メインループ ]--------------------------------------------------------------------
void GameLoop(void){
	while(1){
		WaitSet();				//現在の時間（単位：ミリ秒）取得

		MovePlayer();				//自機移動

		Shoot();				//どこからともなく沸く弾作成

		MoveBullet();				//弾移動

		Show();					//画像表示
		Wait(1000/60);				//メッセージループへ
	}
}
</pre>
<p>第9回と見た目が同じですが、Shoot()とMoveBullet()、Show()の中身を変更しています。（他は全部同じ）</p>
<pre class="brush: cpp;">
//----------[ 弾発射判定 ]----------------------------------------------------------------------
void Shoot(void){
	//1/100の確率で弾作成
	int x,y,angle;
	if(rand()%100==0){
		x=rand()%640;	//x,y座標はランダム
		y=rand()%480;
		angle=GetAngle(x+8,y+8,(int)player.x+16,(int)player.y+16);	//自機への角度取得
		CreateBullet((float)x,(float)y,angle);
	}
}</pre>
<p>ここでは、1/100の確立で適当な位置を出し、自機への角度を取得して、弾を作成しています。<br />
rand()という関数は0～RAND_MAXまでの乱数を返してくれるので、適当な値pで割った余りを出せば、0からpのランダム値が出せます（度数分布が崩れるので本当はあまりよろしくない）。</p>
<p>で、GetAngle(int mx,int my,int sx,int sy);関数は自作関数で、mx,myからsx,syまでの角度を算出します。<br />
GetAngle(x+8,y+8,(int)player.x+16,(int)player.y+16);のx+8,y+8というように、ある値を足していますが、自機などの座標は、画像の左端を表わしているため、画像の中心を表わすためには、画像サイズの半分の値を足せばいいですよね？</p>
<pre class="brush: cpp;">
//----------[ 角度取得 ]------------------------------------------------------------------------
short GetAngle(int mx,int my,int sx,int sy){
	return short(atan2(-double(sy-my),double(sx-mx))/3.141592*180+360)%360;
}
</pre>
<p>この自作関数は上の方で説明したので解りますね（＾＾；</p>
<p>で、弾をアニメーションさせるためには、弾移動中にanimationの値をどんどん増やしていきます。</p>
<pre class="brush: cpp;">
//----------[ 弾移動 ]--------------------------------------------------------------------------
void MoveBullet(void){
	int i;
	float mv=6.0f;

	for(i=0;i&lt;BMAX;i++){
		if(bullet[i].enable==0)	//未使用データだったら次の処理へ
			continue;

		//移動
		bullet[i].x+=fcos[bullet[i].angle]*mv;
		bullet[i].y-=fsin[bullet[i].angle]*mv;

		//アニメーション
		bullet[i].animation++;
		if(bullet[i].animation&gt;=12)
			bullet[i].animation=0;
		//++bullet[i].animation%=12;  と書いても可

		//画面外に出たら未使用データにする
		if(bullet[i].x&lt;-16 || bullet[i].x&gt;640 || bullet[i].y&lt;-16 || bullet[i].y&gt;480)
			bullet[i].enable=0;
	}
}
</pre>
<p>今回使用する画像は<img src="http://takabosoft.com/wp-content/uploads/1999/12/slime2.gif">です。弾のアニメーションは12パターンありますよね？<br />
ですから、animationの値は0～11の間を繰り返し変化するようにプログラムしてやります。</p>
<p>で、後は、このanimationの値をうまく画像に照らし合わせて画面に表示してやればいいわけです。<br />
1つの弾の画像サイズが16×16で左にスライムが32ドットあるので、弾画像のx座標は32+16×アニメーションという事になりますよね？</p>
<pre class="brush: cpp;">
//----------[ 画像表示 ]------------------------------------------------------------------------
void Show(void){
	ClearScreen(lpBack);		//バックバッファ初期化
	DdTextOut(lpBack,0,0,&quot;カーソルキーで移動 pauseキーで終了&quot;,255);

	int i;
	//弾表示
	for(i=0;i&lt;BMAX;i++)
		if(bullet[i].enable==1)
			BltClip(lpBack,(int)bullet[i].x,(int)bullet[i].y,
				16,16,lpWork,32+16*bullet[i].animation,0,1);

	//自機転送
	BltClip(lpBack,(int)player.x,(int)player.y,32,32,lpWork,0,0,1);
	//フリップ
	Flip();
}
</pre>
<p>このアニメーションは、繰り返し行われる弾やら敵に限りますね。<br />
自機のようなキー入力によって画像を変えたいときには使えません。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991231153955.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第10回　キー入力によってキャラクタをジャンプさせる</title>
		<link>http://takabosoft.com/19991222153113.html</link>
		<comments>http://takabosoft.com/19991222153113.html#comments</comments>
		<pubDate>Wed, 22 Dec 1999 06:31:13 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991222153113.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_10.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_10.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_10.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、スペースキーを押すと、スライムがジャンプすると思います。</p>
<p>というわけで今回は、ジャンプについて解説したいと思います。</p>
<h3>ジャンプ</h3>
<p>さて、ジャンプと言うと重力が関係してくるので、なんとなく物理法則（奇数の法則だったかな・・・忘れた）っぽいものを思い浮かべそうですが、そんなムツカシー法則は一切<br />
使いません（爆。第6回 慣性の時と処理が似ている（というかほとんど一緒）のですが、違うのは、Y座標に進む値（今回はsy）に重力として毎回一定の値を足すだけです。</p>
<p>ではサンプルコードを見ていきます。</p>
<p>今回は画面下が地面という設定にします。</p>
<pre class="brush: cpp;">
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	int cx=320-16;
	float cy=448.0f;
	float sy=0.0f;

	//メインループ
	while(1){
		WaitSet();			//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;スペースキーでジャンプ  pauseキーで終了&quot;,255);

		//ジャンプ開始
		if(keyg[32] &amp;&amp; cy==448.0f)
			sy=-12.0f;	//ジャンプ力

		cy+=sy;			//慣性（？
		sy+=0.3f;			//重力を加える

		if(cy&gt;=448)		//画面より下に行かないようにする
			cy=448.0f;

		//キャラクタ表示
		BltClip(lpBack,cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();					//フリップ
		Wait(1000/60);				//メッセージループへ
	}	
</pre>
<p>自然落下は、空から地面に向かって重力によって加速しながら進んでいきますよね？<br />
車で言えば、どんどんアクセル踏んでスピード出して加速していくようなものです。<br />
で、今回のプログラムの場合、syがスピードのようなものになり、重力によってそのスピードが<br />
どんどん増加していく形になります。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991222153113.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第9回　キー入力によって弾を発射させる</title>
		<link>http://takabosoft.com/19991219151646.html</link>
		<comments>http://takabosoft.com/19991219151646.html#comments</comments>
		<pubDate>Sun, 19 Dec 1999 06:16:46 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991219151646.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_09.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_09.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_09.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？カーソルキーで自機が移動し、スペースキーを押すと、弾が発射されると思います。</p>
<p>というわけで今回は、弾処理について解説したいと思います。</p>
<h3>構造化プログラミング基礎</h3>
<p>ここのプログラミング講座を見ている人は、ほとんどが初心者だと思います。<br />
そして初心者にプログラムを組ませてみると、高い確率で、WinMain（またはmain）関数内にひたすら書きまくるようです。<br />
たしかに、それでも動くには動くのですが、プログラムが複雑になってくると、どうしても管理がしにくくなってきますよね？<br />
そこで、ある程度固まった処理、例えば「キー入力でキャラを動かす」だとか「キャラクターを表示する」<br />
というような処理を区切って記述してやります。<br />
そうする事で、プログラムを管理しやすくなったり、バグを発見しやすくなります。</p>
<p>ようするに役割ごとにサブルーチン化するって事です。</p>
<h3>弾処理のアルゴリズム基礎</h3>
<p>弾の発射から消えて無くなるまでの大まかな流れは以下の通りです。</p>
<ol>
<li>弾用の変数（構造体等）を複数、用意する
<li>キー入力があったら、弾データを作成する
<li>弾を移動させる
<li>弾が画面外に出たら、弾データを消す
<li>弾表示
<li>2へ
</ol>
<p>重要なポイントは、弾のデータを作ったら、その後、弾のデータを移動させたり削除したりする事でしょうかねぇ。</p>
<h3>実際にプログラムを組む</h3>
<p>では、判りやすい（たぶん）ように、順番に説明していきたいと思います。</p>
<p>まず、構造化プログラムという事で、必要な変数等をグローバル変数として宣言してやります（ソースの出来るだけ上に書く。</p>
<pre class="brush: cpp;">
//サイン、コサイン テーブル
float fcos[360],fsin[360];

//自機用変数
struct _player{
	float x,y;		//座標
	int renda;		//連打用フラグ
}player={320.0f-32,240.0f-32,0};//初期化

#define BMAX 100		//画面内に最大100弾とする（爆

//弾用変数
struct _bullet{
	BOOL enable;		//使用:1  未使用:0
	float x,y;		//座標
	short angle;		//進む角度
}bullet[BMAX];
</pre>
<p>こうする事で、どこでも（どの自作関数内でも）これらの変数を参照する事が出来るようになります。<br />
今まで、プレイヤーの座標はWinMain関数内でcx,cyと宣言し、それらを使用していましたが、今回は構造体(_player)として宣言します。<br />
_bullet構造体は、弾の処理に使用します。<br />
今回使う変数はとりあえず、使用・未使用を表すフラグ(enable)と<br />
座標（x,y)と、進む方向(angle)だけです。</p>
<p>では、一番最初に実行するWinMain関数を見てみましょう。</p>
<pre class="brush: cpp;">
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	//ウィンドウ作成
	if(!InitWindow(hInstance,hPrevInstance,nCmdShow))
		Quit(&quot;ウィンドウ作成に失敗しました。&quot;);

	//DirectDraw開始
	if(!StartDirectDraw(hw))
		Quit(&quot;DirectDrawの初期化に失敗しました。&quot;);

	//ゲーム初期化
	InitGame();

	//ゲームループへ
	GameLoop();

	return(FALSE);
}
</pre>
<p>いやぁ、今まではここ（↑）にひたすら書いていたので、今回のプログラムは結構すっきり見えますね。</p>
<p>InitWindow()はウィンドウを作成する処理を書いてあるだけの自作関数なので解説省略。</p>
<p>で、DirectDrawを初期化したらInitGame();を呼び出していますね？<br />
これはゲームに必要な処理を先にしておく自作関数です。中を見てみましょう。</p>
<pre class="brush: cpp;">

//----------[ ゲーム初期化 ]--------------------------------------------------------------------
void InitGame(void){
	//ビットマップからパレット読み込み
	LoadPalette(&quot;ss.bmp&quot;);

	//オフスクリーンサーフェイス(lpWork）へビットマップ読み込み
	LoadBitmap(lpWork,&quot;ss.bmp&quot;,0,0,72,64);

	//透明色指定（パレット0番の黒色を透明色とする）
	SetTransColor(lpWork,0);

	//弾データ初期化
	for(int i=0;i&lt;BMAX;i++)
		bullet[i].enable=0;		//全て未使用データとする

	//サイン、コサインテーブル作成
	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}
}
</pre>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/ss.gif" align="left"><br />
まず、作業用サーフェイス(lpWork)にキャラの画像を読み込んでおきます（左の絵）。<br />
で、_bullet構造体のメンバ変数「enable」を0に設定しておきます（全て）。<br />
enableが0だったら、その構造体は使用していないという事にします。</p>
<p>で、ここの処理が終わると、処理はWinMainの方へ戻って、次はGameLoop();を呼び出していますよね？<br />
この関数はゲームのループ用の自作関数です。コードは次のようになっています。</p>
<pre class="brush: cpp;">
//----------[ メインループ ]--------------------------------------------------------------------
void GameLoop(void){
	while(1){
		WaitSet();				//現在の時間（単位：ミリ秒）取得

		MovePlayer();				//自機移動
		Shoot();					//弾発射判定

		MoveBullet();				//弾移動

		Show();					//画像表示
		Wait(1000/60);				//メッセージループへ
	}
}
</pre>
<p>whileの中の最初と最後のWaitSet()、Wait()については既にやっているので解説省略。</p>
<p>で、まずMovePlayer()関数を呼び出していますよね？こいつはキー入力により自機を動かす処理をする自作関数です。</p>
<pre class="brush: cpp;">
//----------[ 自機移動 ]------------------------------------------------------------------------
void MovePlayer(void){

	int angle=-1;				//とりあえず角度を-1にしておく
	//[38]↑	[40]↓	[37]←	[39]→
	if(keyg[38] &amp;&amp; keyg[39])	//右上
		angle=45;
	else
     ・
     ・
     ・
	（略）

	//angleの値が変わっていたらキャラクタの座標を変更する
	float mv=4.0f;	//移動量
	if(angle!=-1){
		player.x+=fcos[angle]*mv;
		player.y-=fsin[angle]*mv;
	}
}
</pre>
<p>この関数内でやっていることは第5回で説明したので、みりゃ判りますね？</p>
<p>ここの処理が終わったら、GameLoopの方へ戻って、次にShoot()関数が呼ばれています。</p>
<p>Shoot()関数では、スペースキーが押されているか判定し、押されていれば弾を作成します。</p>
<p>ここでは、シューティングゲームで使うような、押しっぱなしでも連射できる処理をやります。</p>
<pre class="brush: cpp;">
//----------[ 弾発射判定 ]----------------------------------------------------------------------
void Shoot(void){
	int i;
	//弾発射判定
	if(keyg[32]){			//キーが押されている場合

		if(!player.renda)		//変数rendaが0なら打つ
			for(i=-2;i&lt;=2;i++)	//弾作成
				CreateBullet(player.x+28,player.y-5,90+i*15);

		player.renda++;
		player.renda%=10;		//打つ間隔

	} else {				//キーが押されていない場合
		if(player.renda){
			player.renda++;
			player.renda%=10;
		}
	}
}
</pre>
<p>ここでは、ちょっとした工夫が必要ですかね。<br />
スペースキーが押されていた場合に弾を作成するだけでは、<br />
押している間、毎回弾が作成されてしまいますよね？<br />
シューティングゲームのように、一定間隔で弾を撃ちたい場合は、一つ変数を用意し(今回はplayer.renda)、スペースキーが押されている間はこの変数に1を足し、10になったら0に戻してやります。<br />
で、この変数が0の時だけ弾を作成すれば、処理的には10ループに1回弾を発射する事になります。</p>
<p>さて、ここで極端な例を挙げてみます。この弾を出す間隔が10では無く、1000だとしましょう。<br />
普通、ゲームで発射キーを押しっぱなしにする場合、発射キーを押した瞬間に弾が発射され、一定間隔で弾がでますよね？<br />
で、もし変数「player.renda」が仮に500で止まっていたらどうでしょう。<br />
発射キーを押しても、player.rendaは0でないため、しばらく発射されませんね？</p>
<p>これではマズイので、発射キーが押されていない時、player.rendaが0で無い場合には、player.rendaに1を足し、ある数を越えたら0にする、といった処理を付け加えてやります。こうすれば、発射キーを離している間でもplayer.rendaの値が変化するので、発射キーを離し、しばらくして発射キーを押した瞬間に、弾を発射させる事が可能となります。</p>
<p>ついでですが、発射キーが押されていない時にplayer.rendaを0にすると、いつでも発射キーを押した瞬間に弾が出せます（実際にやってみてください）。</p>
<p>ここの関数内では弾を作成する時にCreateBullet( x座標 , y座標 , 角度(0～359) )自作関数を呼び出しています。<br />
ではどういった処理をしているのか見てみましょう。</p>
<pre class="brush: cpp;">
//----------[ 弾作成 ]--------------------------------------------------------------------------
void CreateBullet(float x,float y,short angle){
	int i;
	int no=-1;	//作成する弾のデータ番号

	//構造体の中で、空いている（未使用）データを探す
	for(i=0;i&lt;BMAX;i++)
		if(bullet[i].enable==0){
			no=i;
			break;
		}

	//空いているデータが見つからなかったら作成せずに戻る
	if(no==-1)
		return;

	//データ詰める
	bullet[no].x=x;
	bullet[no].y=y;
	bullet[no].angle=angle;
	bullet[no].enable=1;	//使用中にする
}
</pre>
<p>さて、初めの方で呼び出したInitGame()内では_bullet構造体の全てのenableを0にし、未使用データとしましたね？<br />
ですから、ここ(CreateBullet)では、構造体の配列の0番からBMAX-1（今回は99）までを順に参照し、enableが0（つまり未使用となっているデータ）を探します。</p>
<p>そんでもって、空いているデータが見つかったら、その番号の構造体に情報を詰め込んでやります。<br />
最後にenableを1とし、no番の構造体を使用データという事にしていますよね？<br />
ですから、次にCreateBullet関数内で空いているデータを順に探した時も、no番のenableは0では無いので<br />
上から新しくデータが詰められる事はありません。</p>
<p>でShoot()関数へ戻り、GameLoop()の方へ戻り、次にMoveBullet()を呼び出しています。</p>
<pre class="brush: cpp;">
//----------[ 弾移動 ]--------------------------------------------------------------------------
void MoveBullet(void){
	int i;
	float mv=6.0f;

	for(i=0;i&lt;BMAX;i++){
		if(bullet[i].enable==0)	//未使用データだったら次の処理へ
			continue;

		//移動
		bullet[i].x+=fcos[bullet[i].angle]*mv;
		bullet[i].y-=fsin[bullet[i].angle]*mv;

		//画面外（今回は画面上）に出たら未使用データにする
		if(bullet[i].y&lt;-32)
			bullet[i].enable=0;
	}
}
</pre>
<p>ここでは、構造体_bulletの配列を0から順番に参照し、enableが0でないデータ（つまり使用しているデータ）だけを処理します。<br />
角度による移動自体は第4回で説明したので省略。</p>
<p>そして、画面外に出たらenableを0にし、未使用データとしてやります。</p>
<p>GameLoop()に戻り、次にShow()が呼び出されます。これは、ディスプレイに一連の表示をする<br />
自作関数です。<br />
コードを見てみましょう。</p>
<pre class="brush: cpp;">
//----------[ 画像表示 ]------------------------------------------------------------------------
void Show(void){
	ClearScreen(lpBack);		//バックバッファ初期化
	DdTextOut(lpBack,0,0,&quot;ｶｰｿﾙｷｰで移動 spaceｷｰで弾発射 pauseキーで終了&quot;,255);

	int i;
	//弾表示
	for(i=0;i&lt;BMAX;i++)
		if(bullet[i].enable==1)
			BltClip(lpBack,(int)bullet[i].x,(int)bullet[i].y,8,32,lpWork,64,0,1);

	//自機転送
	BltClip(lpBack,(int)player.x,(int)player.y,64,64,lpWork,0,0,1);
	//フリップ
	Flip();
}
</pre>
<p>まぁ、ここは見りゃ判りますね（爆</p>
<p>弾は使用しているものだけ表示してやります。</p>
<p>とりあえず、これで説明は終わりです（＾＾；</p>
<p>今回のプログラムの流れを図で表すと、こんな感じになるのかも</p>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/9arg.gif"></p>
<p>矢印は関数の呼び出しです。</p>
<h3>おまけ</h3>
<p>今回は弾のデータ構造に構造体の配列を使用しましたが、この場合、配列中に使用しているデータと使用していないデータが両方含まれていたり、弾数に制限があったりして少し不便ですよね？<br />
そんなときはリスト構造を使用すると結構スッキリ記述できます。<br />
Space Soldierは双方向リスト構造でやってました。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991219151646.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第8回　座標同士の当たり判定</title>
		<link>http://takabosoft.com/19991215151157.html</link>
		<comments>http://takabosoft.com/19991215151157.html#comments</comments>
		<pubDate>Wed, 15 Dec 1999 06:11:57 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991215151157.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_08.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_08.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_08.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、何かキーを押すと、そのキーコードが画面に表示されると思います。<br />
というわけで今回は、キー入力について解説したいと思います。</p>
<h3>二次元座標同士の当たり判定</h3>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/hit.gif" align="right"><br />
右の図のように、座標上に2つの長方形が存在したとします。<br />
ここで、この2つの物体（長方形）が重なっているか（当たっているか）を調べるときに、次のような式をよく使います。<br />
<br clear="all"></p>
<pre class="brush: cpp;">
if(ax2&lt;bx1 || ax1&gt;bx2 || ay1&lt;by2 || ay2&gt;by1)
	当たっていない
else
	当たっている
</pre>
<p>ただ、例によって（？）数学上の座標とパソコンのディスプレイの座標ではY軸の向きが逆なので、</p>
<pre class="brush: cpp;">
if(ax2&lt;bx1 || ax1&gt;bx2 || ay1&gt;by2 || ay2&lt;by1)
	当たっていない
else
	当たっている
</pre>
<p>これが正しい式となります。まぁ、当たった時の判定だけしたいのならば、「!」を使えば直ぐに出来ますね。</p>
<pre class="brush: cpp;">
if(!(ax2&lt;bx1 || ax1&gt;bx2 || ay1&gt;by2 || ay2&lt;by1))
	当たっている
</pre>
<p>実際にゲームに使用する場合は、キャラクターの全体もしくは、その一部を当たり判定とし、その部分を長方形として扱い、上の式へ代入します。<br />
ただ、この方法はシューティングゲーム等のような、「あまり精度にはこだわらないから速度の速い方法がいい」という場合に使用してください。</p>
<h3>レッツプログラミング</h3>
<p>ではサンプルコードを見ていきます。</p>
<p>今回は、「第3回 キー入力によって複数のキャラクタを動かす」の最後の方へちょこっと加え、2匹のスライムが重なった（当たった）時にそのメッセージを表示するようにします。</p>
<pre class="brush: cpp;">
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	//座標
	int cx[2]={160-16,480-16},cy[2]={240-16,240-16};

	//キーテーブル
	unsigned char key_up[2]={38,104},key_down[2]={40,98},
		key_left[2]={37,100},key_right[2]={39,102};

	//その他の変数
	int i,mv=3;

	//メインループ
	while(1){
		WaitSet();			//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;カーソルキー、テンキー（4,6,8,2)で移動 pauseキーで終了&quot;,255);

		for(i=0;i&lt;2;i++){
			//キャラクター移動
			if(keyg[key_up[i]])	//↑
				cy[i]-=mv;
			else
			if(keyg[key_down[i]])	//↓
				cy[i]+=mv;
			else
			if(keyg[key_left[i]])	//←
				cx[i]-=mv;
			else
			if(keyg[key_right[i]])	//→
				cx[i]+=mv;

			//キャラクタ表示
			BltClip(lpBack,cx[i],cy[i],32,32,lpWork,32*i,0,1);
		}

		//当たり判定
		if(!(cx[0]&gt;cx[1]+31 || cx[0]+31&lt;cx[1] || cy[0]+31&lt;cy[1] || cy[0]&gt;cy[1]+31))
			DdTextOut(lpBack,0,16,&quot;当たっている&quot;,255);

		Flip();					//フィリップ
		Wait(1000/60);				//メッセージループへ
	}
</pre>
<p>解説するまでも無いですね。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991215151157.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第7回　グラディウスのオプションみたいな処理をする</title>
		<link>http://takabosoft.com/19991213150725.html</link>
		<comments>http://takabosoft.com/19991213150725.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 06:07:25 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213150725.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_07.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_07.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_07.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、何かキーを押すと、そのキーコードが画面に表示されると思います。<br />
というわけで今回は、キー入力について解説したいと思います。</p>
<h3>よくわからない解説</h3>
<p>ではサンプルコードを見ていきます。</p>
<pre class="brush: cpp;">

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

#define CMAX 81	//記憶しておく座標の数
#define OSTEP 20	//オプションを配置する間隔

	//    cx:座標 cy:座標  mv:移動量
	float cx[CMAX],cy[CMAX],mv=3.0f;

	//座標初期化
	int i;
	for(i=0;i&lt;CMAX;i++){
		cx[i]=float(320-16);
		cy[i]=float(240-16);
	}

	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;↑、↓、→、←で移動 pauseキーで終了&quot;,255);

		angle=-1;	//とりあえず角度を-1にしておく

		//[38]↑	[40]↓	[37]←	[39]→
		if(keyg[38] &amp;&amp; keyg[39])	//右上
			angle=45;
		else
		・
		・
		・
		&lt;キー入力省略&gt;

		//angleの値が変わっていたらキャラクタの座標を変更する
		if(angle!=-1){

			//座標を記憶しておく（バックアップ
			for(i=CMAX-2;i&gt;=0;i--){
				cx[i+1]=cx[i];
				cy[i+1]=cy[i];
			}

			cx[0]+=fcos[angle]*mv;
			cy[0]-=fsin[angle]*mv;
		}

		//キャラクタ表示
		//オプション
		//後ろ（古い座標）から表示
		for(i=CMAX-1;i&gt;0;i-=OSTEP)
			BltClip(lpBack,(int)cx[i],(int)cy[i],32,32,lpWork,32,0,1);

		//本体
		BltClip(lpBack,(int)cx[0],(int)cy[0],32,32,lpWork,0,0,1);

		Flip();					//フィリップ
		Wait(1000/60);				//メッセージループへ
	}
</pre>
<p>グラディウスのオプションは、本体（自機）の通った跡を遅れて通りますよね？<br />
ですから、こういった処理は、自機の座標をいくつか記憶しておき、古い座標の所へオプションを配置してやればいいのです。</p>
<p>まず、キャラクタ（自機）の座標を配列でいくつか確保してやります。</p>
<pre class="brush: cpp;">
#define CMAX 81	//記憶しておく座標の数
float cx[CMAX],cy[CMAX];
</pre>
<p>このとき、一番新しい座標をcx[0],cy[0]とし、一番古い座標をcx[CMAX-1],cy[CMAX-1]とします。<br />
ゲーム中、キー入力があり、自機の座標が変更されようとしたら、cx[0],cy[0]～cx[CMAX-2],cy[CMAX-2]の中身を<br />
cx[1],cy[1]～cx[CMAX-1],cy[CMAX-1]へコピーします（つまり1つ後ろへずらす）。<br />
それで、cx[0],cy[0]の値を変更してやればいいのです。</p>
<pre class="brush: cpp;">
	if(angle!=-1){
		//座標を記憶しておく（バックアップ
		for(i=CMAX-2;i&gt;=0;i--){
			cx[i+1]=cx[i];
			cy[i+1]=cy[i];
		}
		cx[0]+=fcos[angle]*mv;
		cy[0]-=fsin[angle]*mv;
	}
</pre>
<p>座標を一つ後ろへずらす時、</p>
<pre class="brush: cpp;">
	for(i=0;i&lt;CMAX-1;i++){
		cx[i+1]=cx[i];
		cy[i+1]=cy[i];
	}
</pre>
<p>こんなふうに前からコピーしてしまうと、cx[0]の値がcx[1]へ、cx[1]の値がcx[2]へ・・・というように<br />
全て同じ値になってしまうので、やめましょう（爆。ちゃんと後ろからコピーしていきましょう。</p>
<p>で、自機の座標跡を作っていったら、次は表示ですね。</p>
<pre class="brush: cpp;">
#define OSTEP 20	//オプションを配置する間隔

	&lt;略&gt;

	//後ろ（古い座標）から表示
	for(i=CMAX-1;i&gt;0;i-=OSTEP)
		BltClip(lpBack,(int)cx[i],(int)cy[i],32,32,lpWork,32,0,1);

	//本体
	BltClip(lpBack,(int)cx[0],(int)cy[0],32,32,lpWork,0,0,1);
</pre>
<p>オプション用の画像は自機の座標跡の古い所から表示してやります。<br />
優先度の問題なのですが、先に描いた方が画面の後ろに表示されますよね？<br />
なので、新しい座標の所程、後で描いてやります（自機は一番最後）</p>
<p>上のようなプログラムだと、<br />
80,60,40,20番目の座標へ順番にオプションを配置し0番の座標へ自機を配置しています。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213150725.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第6回　慣性の法則を使用したキャラクタ移動</title>
		<link>http://takabosoft.com/19991213150046.html</link>
		<comments>http://takabosoft.com/19991213150046.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 06:00:46 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213150046.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_06.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_06.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_06.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、カーソルキーを押すと、キャラクターが動くと思います。<br />
というわけで今回は、慣性の法則を使用したキャラ移動について解説したいと思います。</p>
<h3>慣性の法則とは？</h3>
<p>慣性の法則とは、私も詳しくは知りませんが、「物体は同じ運動を続けようとする」とかそんな感じの法則だった気がします。<br />
宇宙空間のような摩擦の少ない場所で、一度動き出すと何かにぶつかったりするまで動き続けるというやつです。<br />
スケートで一度動き出したらなかなか止まらない現象も同じですね。<br />
詳しく知りたいなら物理の先生とかに聞いてください。</p>
<p>過去のゲームでも慣性を使用したゲームなんて腐るほどありますよね？<br />
月面着陸ゲームだとか。<br />
Bio100%さんの蟹味噌もそうですね。<br />
使い道はいろいろあるので覚えておいても損は無いでしょう。</p>
<h3>実際にプログラムを組む</h3>
<p>ではサンプルコードを見ていきます。</p>
<pre class="brush: cpp;">
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	//    cx:座標     cy:座標 mv:加速度
	float cx=320.0f-16,cy=240.0f-16,mv=0.1f;
	float cmx=0.0f,cmy=0.0f;//移動量

	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;↑、↓、→、←で移動 pauseキーで終了&quot;,255);

		angle=-1;	//とりあえず角度を-1にしておく

		//[38]↑	[40]↓	[37]←	[39]→
		if(keyg[38] &amp;&amp; keyg[39])	//右上
			angle=45;
		else
		if(keyg[37] &amp;&amp; keyg[38])	//左上
			angle=135;
		else
		if(keyg[37] &amp;&amp; keyg[40])	//左下
			angle=225;
		else
		if(keyg[40] &amp;&amp; keyg[39])	//右下
			angle=315;
		else
		if(keyg[38])	//↑
			angle=90;
		else
		if(keyg[40])	//↓
			angle=270;
		else
		if(keyg[37])	//←
			angle=180;
		else
		if(keyg[39])	//→
			angle=0;

		//angleの値が変わっていたら移動量を変更する
		if(angle!=-1){
			cmx+=fcos[angle]*mv;
			cmy-=fsin[angle]*mv;
		}

		cx+=cmx;
		cy+=cmy;

		//跳ね返る
		if(cx&lt;0){		//左
			cx=0.0f;
			cmx=-cmx;
		}
		if(cx&gt;640-32){	//右
			cx=float(640-32);
			cmx=-cmx;
		}
		if(cy&lt;0){		//上
			cy=0.0f;
			cmy=-cmy;
		}
		if(cy&gt;480-32){	//下
			cy=float(480-32);
			cmy=-cmy;
		}

		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}
</pre>
<p>では、順番に解説して行きたいと思います。</p>
<pre class="brush: cpp;">
	float cmx=0.0f,cmy=0.0f;		//移動量（慣性
	&lt;略&gt;
	cx+=cmx;
	cy+=cmy;
</pre>
<p>移動量(cmx,cmy)は毎回キャラクタの座標を移動させる値です。<br />
例えば、cmxを5.0に設定しておけばキャラは右へ移動し続けます。<br />
で、キー入力により、この移動量を変えることによって、慣性の法則のようなものが実現できるというわけです。</p>
<p>今まではキー入力でそのままキャラの座標を変更していましたが、慣性を使用する場合はキー入力で移動量を変化させ、移動量によって毎回キャラクターを移動させます。</p>
<p>それから、ついでなのでキャラクターを画面の端で跳ね返らせる事にします（そうしないと直ぐ画面外に行ってしまう）。</p>
<pre class="brush: cpp;">
		//跳ね返る
		if(cx&lt;0){		//左
			cx=0.0f;
			cmx=-cmx;
		}
		if(cx&gt;640-32){	//右
			cx=float(640-32);
			cmx=-cmx;
		}
		if(cy&lt;0){		//上
			cy=0.0f;
			cmy=-cmy;
		}
		if(cy&gt;480-32){	//下
			cy=float(480-32);
			cmy=-cmy;
		}
</pre>
<p>例えば、左端の壁を越えてしまった（cxが0より小さくなった）場合は、まずcxを0にします。<br />
これをやらないとキャラクターが埋まってしまったり、帰ってこなくなる場合があるからです（＾＾；<br />
で、移動量（cmx）の符号を反転させてやります。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213150046.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第5回　キー入力によってキャラクタを8方向へ動かす</title>
		<link>http://takabosoft.com/19991213145544.html</link>
		<comments>http://takabosoft.com/19991213145544.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 05:55:44 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213145544.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_05.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_05.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_05.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、カーソルキーでキャラクタが8方向へ移動すると思います。</p>
<p>というわけで、今回は4つのキー入力で8方向へキャラクタを移動させる方法を説明したいと思います。</p>
<h3>やってはいけないのかも？</h3>
<p>「ゲーム制作基礎 第2回」では4つのキー入力でキャラクタを4方向に動作させましたが、8方向というとよくこんな感じでやってしまいます（第2回のキー入力部分のelseを外しただけ）。</p>
<pre class="brush: cpp;">
int cx=320-16,cy=240-16,mv=3;

	略

	//キャラクター移動
	if(keyg[38])	//↑
		cy-=mv;
	if(keyg[40])	//↓
		cy+=mv;
	if(keyg[37])	//←
		cx-=mv;
	if(keyg[39])	//→
		cx+=mv;

	//キャラクタ表示
	BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
</pre>
<p>たしかに、これでも8方向の移動は可能ですが、例えば右上に移動する場合はどうでしょう？<br />
mvが3として、右に3、上に3移動しますよね？直線距離で考えると4.24264068・・・というように3よりも移動距離が大きい事になります。<br />
ですから、このプログラムでは斜めに移動する場合、移動量がおかしいという事になります。<br />
（あえてこれにするという考えも有りだと思いますが。）</p>
<h3>キャラクターの8方向移動</h3>
<p>というわけで、8方向、同じ距離で進ませるには、三角関数を使用すると、比較的簡単に出来ます。<br />
三角関数については前回みっちりやったので大丈夫ですね？</p>
<pre class="brush: cpp;">
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	//    cx:座標 cy:座標 mv:移動量
	float cx=320.0f-16,cy=240.0f-16,mv=3.0f;

	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;↑、↓、→、←で移動 pauseキーで終了&quot;,255);

		angle=-1;	//とりあえず角度を-1にしておく

		//[38]↑	[40]↓	[37]←	[39]→
		if(keyg[38] &amp;&amp; keyg[39])	//右上
			angle=45;
		else
		if(keyg[37] &amp;&amp; keyg[38])	//左上
			angle=135;
		else
		if(keyg[37] &amp;&amp; keyg[40])	//左下
			angle=225;
		else
		if(keyg[40] &amp;&amp; keyg[39])	//右下
			angle=315;
		else
		if(keyg[38])	//↑
			angle=90;
		else
		if(keyg[40])	//↓
			angle=270;
		else
		if(keyg[37])	//←
			angle=180;
		else
		if(keyg[39])	//→
			angle=0;

		//angleの値が変わっていたらキャラクタの座標を変更する
		if(angle!=-1){
			cx+=fcos[angle]*mv;
			cy-=fsin[angle]*mv;
		}

		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}
</pre>
<p>まず1つ角度を表す変数を用意しておき(angle)、それをまず-1に設定しておきます。<br />
そしてキー判定によって、そのキーが示す角度へangleを変更します。<br />
もしangleが最初に設定した-1とは違う値だったら、キーが押されていた事になり、そのangleが示す方向へ移動させてやれば良いというわけです。<br />
ただ、気を付けないといけないのが、キー判定の順番です。<br />
斜め移動のキー判定を先にやってからでないと、いけません。<br />
実際にいろいろいじってみれば何故かが解ると思います。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213145544.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第4回　指定した角度の方向へキャラクタを動かす</title>
		<link>http://takabosoft.com/19991213144957.html</link>
		<comments>http://takabosoft.com/19991213144957.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 05:49:57 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213144957.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_04.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_04.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_04.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、カーソルの左右で角度を変更でき、上でその角度の方向へキャラクタが移動すると思います。</p>
<h3>三角関数のお勉強 ステップ1</h3>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/sankaku0.gif" align="left"><br />
さて、三角関数とはなんぞや？という人のためにササっと解説してしまいます。<br />
（高校2までに習うとは思いますが・・・）</p>
<p>左の図形を見てください。こういった<b>直角三角形</b>がある場合、</p>
<p>  ① sinΘ=b/c</p>
<p>  ② cosΘ=a/c</p>
<p>  ③ tanΘ=b/a</p>
<p>といった定義が存在します。ちなみに、①から順番に、サイン・コサイン・タンジェントと読みます。</p>
<p>ここでよくある(?)問題</p>
<p>A君は、とある木から10m離れた所で、地面に突っ伏し、そこから<br />
丁度45度の角度でその木の一番高い所を見ていました。<br />
さて、この木の高さは何mでしょう。</p>
<p>まぁ、この場合↑の図形で言う所のbが高さ、aが10m（木とA君の距離）ですね。<br />
で、bを求めたくて、解っているのがaと45度という角度なので③の「tanΘ=b/a」を使って解いてやればいいですね？</p>
<p>  tan45°=b/10</p>
<p>  b=tan45°×10</p>
<p>  b=1×10</p>
<p>  答え 10m</p>
<p>となります。<br />
まぁ「tan45°」のように<br />
解りやすい角度なら頭の中でも計算できますが「tan2°」だと、直ぐ答えれる人はなかなかいないでしょう（たぶん。<br />
幸い、C言語にはあらかじめ、sin,cos,tanの関数が用意されているので、値を簡単に出せます。</p>
<h3>三角関数のお勉強 ステップ2</h3>
<p><img src="http://takabosoft.com/wp-content/uploads/1999/12/sankaku1.gif" align="left"><br />
さて、基本が解ったところで（？）左のような<B>半径1の円</B>上にある点Pの座標（a,b）を表わすにはどうすればいいでしょう（かなり強引）。</p>
<p>こいつらも三角関数を使用してやります。</p>
<p>まず、点pのX座標がa、原点から点pまでの長さが1（半径1より）という事で</p>
<p>  cosΘ=a/1</p>
<p>  a=cosΘ</p>
<p>となります。</p>
<p>同様に、点pのY座標がb、原点から点pまでの長さが1という事で</p>
<p>  sinΘ=b/1</p>
<p>  b=sinΘ</p>
<p>となります。よって、半径1の円上にある点Pの座標はP（cosΘ、sinΘ）と表わす事が出来るわけです。</p>
<h3>三角関数のお勉強 ステップ3</h3>
<p>さて、ここで問題（またかよー）。点P（cx,cy）がΘ°の方向へ1進んだ時の座標はどう表されるでしょう？</p>
<p>もうお分かりですね？半径1の円上にある点Pの座標はP（cosΘ、sinΘ）という事から、</p>
<p>  P（ cx+cosΘ , cy+sinΘ ）</p>
<p>となりますね。また、進みたい距離が1ではなく t だとしたら</p>
<p>  P（ cx+cosΘ×t , cy+sinΘ×t ）</p>
<p>という事になります。</p>
<h3>C言語で三角関数を使用する方法</h3>
<p>ここまでで、三角関数の基本はバッチリ身に付いたと思います（そんなことないってか？）。<br />
C言語で三角関数を使用するには、まず「math.h」をインクルードしてやります（まぁ当たり前ですね）。</p>
<p>それでは「三角関数のお勉強ステップ3」をそのままC言語で表してみましょうか。</p>
<p>キャラクターの座標が cx , cy の時、 angle 度の方向へ t 進みたい場合、</p>
<pre class="brush: cpp;">
cx+=cos(angle)*t;
cy+=sin(angle)*t;
</pre>
<p>と、なるはずですが、パソコンのディスプレイ上の y 座標は、下に行く程増加していく、つまり数学上の座標とはy軸の向きが反対になります。<br />
ですから、</p>
<pre class="brush: cpp;">
cx+=cos(angle)*t;
cy-=sin(angle)*t;
</pre>
<p>でOK・・・となるはずですが、まだです（爆</p>
<p>C言語の cos , sin 等の引数はラジアンという単位で指定しなければなりません。<br />
まぁラジアンについては私もよく知らないので数学の先生に聞いてください。<br />
とにかく180°はラジアンでπと表わされるので、<br />
1°はπ/180ですよね？<br />
よって</p>
<pre class="brush: cpp;">
cx+=cos(angle*3.1415926535/180)*t;
cy-=sin(angle*3.1415926535/180)*t;
</pre>
<p>これで、一応動作します。</p>
<h3>ちょっとした高速化のお話</h3>
<p>さて、C言語で毎回 cos() , sin() とかを呼び出してごちゃごちゃ計算させるのは処理の無駄であり、<br />
ゲームの速度を落とす原因となりかねません。<br />
ですから、ちょっと工夫して高速化させてやります。</p>
<p>と言っても、ただあらかじめテーブル作成しておくだけなんですけどね。</p>
<pre class="brush: cpp;">
	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}
</pre>
<p>こいつらを作成してやれば</p>
<pre class="brush: cpp;">
cx+=cos(angle*3.1415926535/180)*t;
cy-=sin(angle*3.1415926535/180)*t;
</pre>
<p>が</p>
<pre class="brush: cpp;">
cx+=fcos[angle]*t;
cy-=fsin[angle]*t;
</pre>
<p>で済みます。<br />
ただ、角度angleは0～359の範囲でないといけないし、<br />
0.5といった少数で指定する事も出来なくなります。<br />
ですが、シューティングゲームのように、速度重視且つ、 sin , cos で精度にあまりとらわれなくても良いような場合はテーブルでやった方が良いですね。</p>
<h3>サンプルコードの解説</h3>
<p>では、サンプルコードを見てみましょう。</p>
<pre class="brush: cpp;">
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	//    cx:座標 cy:座標 mv:移動量
	float cx=320.0f-16,cy=240.0f-16,mv=3.0f;

	// angle:向いている角度
	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i&lt;360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;←、→で旋回 ↑で進む pauseキーで終了&quot;,255);
		sprintf(tmp,&quot;現在向いている角度 %d&quot;,angle);
		DdTextOut(lpBack,0,16,tmp,255);

		//旋回
		if(keyg[37]){	//←
			angle+=5;
			//cos,sinは配列を使用しているので角度が0～359になるように調整する必要がある
			if(angle&gt;=360)
				angle-=360;
			//angle%=360;と書ける
		}
		if(keyg[39]){	//→
			angle-=5;

			if(angle&lt;0)
				angle+=360;
			//angle=(angle+360)%360;と書ける(たぶん
		}

		//向いている角度へ進む
		if(keyg[38]){	//↑
			cx+=fcos[angle]*mv;
			cy-=fsin[angle]*mv;
		}

		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}	
</pre>
<p>キャラクタの座標（cx,cy)はfloat型で宣言します<br />
（「int a=0; a+=0.5」とやってもaの値は0のままというのは当たり前ですからね）。<br />
んー、まぁ後は大体上の方で説明したので解ります。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213144957.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第3回　キー入力によって複数のキャラクタを動かす</title>
		<link>http://takabosoft.com/19991213143859.html</link>
		<comments>http://takabosoft.com/19991213143859.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 05:38:59 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213143859.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_03.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_03.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_03.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、スライムが2個表示されると思います。<br />
というわけで今回は、キー入力により複数のキャラを動かす処理ついて解説したいと思います。</p>
<h3>複数キャラの扱い</h3>
<p>今回は、こいつら<IMG src="http://takabosoft.com/wp-content/uploads/1999/12/slime1.gif">（slime.bmp 64x32）を使用して複数のキャラクタを表示、移動させてみたいと思います。</p>
<p>というわけでサンプルコードを見てみます。</p>
<pre class="brush: cpp;">
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	//座標
	int cx[2]={160-16,480-16},cy[2]={240-16,240-16};

	//キーテーブル
	unsigned char key_up[2]={38,104},key_down[2]={40,98},
		key_left[2]={37,100},key_right[2]={39,102};

	//その他の変数
	int i,mv=3;

	//メインループ
	while(1){
		WaitSet();			//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;カーソルキー、テンキー（4,6,8,2)で移動 pauseキーで終了&quot;,255);

		for(i=0;i&lt;2;i++){
			//キャラクター移動
			if(keyg[key_up[i]])	//↑
				cy[i]-=mv;
			else
			if(keyg[key_down[i]])	//↓
				cy[i]+=mv;
			else
			if(keyg[key_left[i]])	//←
				cx[i]-=mv;
			else
			if(keyg[key_right[i]])	//→
				cx[i]+=mv;

			//キャラクタ表示
			BltClip(lpBack,cx[i],cy[i],32,32,lpWork,32*i,0,1);
		}

		Flip();				//フィリップ
		Wait(1000/60);			//メッセージループへ
	}
</pre>
<p>初心者だと、つい1キャラづつ変数用意（例えば int,cx1,cx2,cx3・・・）し、処理も1キャラづつ用意しそうですが、そんな必要はありません。</p>
<p>座標等の変数を配列（もしくはクラス・構造体の配列）で用意する事により、ループ処理で複数のキャラを処理できるというわけです。</p>
<h3>キーボードのお話</h3>
<p>今回は2つのキャラクターを同時に表示し、キー入力も4方向×2キャラという事で8つのキーを扱っているのですが、AT互換機（DOS/V）でキーボードを一気にたくさん押すと、一部のキーの反応が無くなる場合があります（PC-9821シリーズでは大丈夫なようですが）。<br />
スペースソルジャーでも、2人で同時に遊んでいると、どちらかの機体が左上に行けなくなるという現象が起きました。<br />
これはハードウェアの仕様という事でプログラマーにはどうする事も出来ないようです。<br />
そういった場合にはジョイパッドに逃げるしかありませんね（＾＾；</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213143859.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第2回　キー入力によってキャラクタを4方向へ移動させる</title>
		<link>http://takabosoft.com/19991213142634.html</link>
		<comments>http://takabosoft.com/19991213142634.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 05:26:34 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213142634.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_02.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_02.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_02.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、カーソルキーを押すと、スライム（？）が移動すると思います。<br />
というわけで今回は、キー入力によるキャラクタ移動について解説したいと思います。</p>
<h3>キャラクタの表示</h3>
<p>では、まずキー入力の前に、キャラクターの表示について簡単に説明します。<br />
まず、キャラクター<img src="http://takabosoft.com/wp-content/uploads/1999/12/slime0.gif">（slime.bmp 32x32）を作業用バッファ（lpWork）に読み込んでおきます。<br />
画面に表示するだけなら、作業用バッファからプライマリサーフェイスへBltFastとかで転送すれば、それで終わりですが、今回は、キャラクタの座標が変わったら、新しい座標へ移動するようなプログラムを組まなければなりません。<br />
単に毎回キャラクタの座標へキャラクタを表示し続けるだけでは、元の座標に描かれていた絵がそのまま残ってしまいますよね？<br />
そこで、多少工夫（？）し、残骸が残らないようにしてやる必要があります。</p>
<p>今回のサンプルでは、もっとも簡単な方法でそれらを実現しています。<br />
ただ、バックバッファを全部消し、キャラを表示、フリップするだけ。<br />
これなら、毎回バックバッファを全消去しているため、キャラの残骸が残らないと言うわけです。</p>
<p>見れば解りますね（＾＾；説明するまでもないと思いましたが・・・。</p>
<pre class="brush: cpp;">
int cx,cy;//キャラクタのx,y座標

略
	ClearScreen(lpBack);
	BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
	Flip();
</pre>
<h3>やってはいけない？</h3>
<p>さて、キー入力があった場合にウィンドウプロシージャが呼ばれることは前回解ったと思います。<br />
で、実際にキー入力があった場合にキャラクタを移動させるようなプログラムを組むにはどうすればいいでしょう？<br />
まず、初心者がよく考えそうな事は、キャラクタの座標をグローバル変数で用意し、ウィンドウプロシージャが呼ばれて、特定のキーが押されたら、そこで座標を変更するというもの。</p>
<p>実際に組むと、たぶんこんな感じ。<br />
（キーコードについては前回のプログラムを実行して調べてください（爆））</p>
<pre class="brush: cpp;">
int cx,cy;//キャラクタのx,y座標

略

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){

	switch(iMessage){

	略

	//キー押された
	case WM_KEYDOWN:
		if(wParam==37)	//「←」キー
			cx-=5;
		else
		if(wParam==39)	//「→」キー
			cx+=5;
		・
		・
		・
		ClearScreen(lpBack);
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();

	return(TRUE);
</pre>
<p>この方法でも、キャラクタは動くには動きます。<br />
さて、テキストで文字を打つときの事を思い浮かべてください。<br />
'A'キーをずっと押しっぱなしにしていると、'A'と一度表示され、しばらくしてから連続的に'A'が表示されると思います。<br />
ウィンドウプロシージャもこのタイミングで呼ばれるため、キャラクタはカクカク動作する事になり、非常にカッコ悪いし、ゲームにも使えません。</p>
<h3>仮想キーマップのお話</h3>
<p>というわけで、↑の問題を解決すべく、仮想キーマップなるものを作成します。<br />
まぁ、これは私が勝手に考えたものなので正しい方法とは言い切れません（爆。</p>
<p>では、プログラムを見てみましょう。</p>
<pre class="brush: cpp;">

/*  グローバル変数群   */
char keyg[256];	//仮想キーマップ

略

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){

	略

	//キー押された
    	case WM_KEYDOWN:
		//pauseキー(キーコード19）が押されたら終了
		if(wParam==19)
			Quit();
		keyg[wParam]=1;
		return(TRUE);

	//キー離された
	case WM_KEYUP:
		keyg[wParam]=0;
		return(TRUE);
</pre>
<p>では、まず256個の配列を確保します（keyg）。<br />
キーが押され「WM_KEYDOWN」が送られてきたらkeyg[wParam]に1を代入します（wParamは、キーコードを表します）。<br />
で、キーが離され「WM_KEYUP」が送られてきたらkeyg[wParam]に0を代入します。<br />
これだけです（＾＾；これで、ウィンドウプロシージャ以外の関数内でもkeyg[キーコード]を参照する事によって<br />
そのキーが押されている（値が1）のか、押されていないか（値が0）を判断出来ると言うわけです。</p>
<h3>同期のお話</h3>
<p>さて、いつでもキーの状態を取得出来るようになったので、実際にちょっと組んでみましょうか。<br />
こんな感じですかねぇ。</p>
<pre class="brush: cpp;">

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	MSG msg;

	&lt;略&gt;

	int cx=320-16,cy=240-16,mv=3;
	//メインループ
	while(1){
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;カーソルキーで移動 pauseキーで終了&quot;,255);

		//キャラクター移動
		if(keyg[38])	//↑
			cy-=mv;
		else
		if(keyg[40])	//↓
			cy+=mv;
		else
		if(keyg[37])	//←
			cx-=mv;
		else
		if(keyg[39])	//→
			cx+=mv;

		//キャラクタ表示
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ

		//メッセージループ
		while(PeekMessage(&amp;msg,NULL,0,0,PM_NOREMOVE)){
			if(!GetMessage(&amp;msg,NULL,0,0))
				Quit();
			TranslateMessage(&amp;msg);
			DispatchMessage(&amp;msg);
		}
	}
</pre>
<p>このプログラムには欠点があります。<br />
それは、マシンの処理速度（描画速度）が速ければ速い程、キャラクタの移動速度も<br />
上がってしまうという事です。つまり、処理速度の違うパソコンによってゲーム速度が変わって<br />
しまうというのです（まぁフリップである程度抑えられますが・・・）。</p>
<p>というわけで、疑似タイマー処理（？）を紹介します。以前「DirectDraw基礎 第5回」でも書いたのですが、もうちょっと変更してみます（＾＾；</p>
<p>プログラムはこんな感じです（今回のサンプルコードと同じ）</p>
<pre class="brush: cpp;">
/*  グローバル変数群 */
DWORD wait_time;	//ウェイト用

略

//----------[ ウェイト開始時間設定（謎 ]--------------------------------------------------------
void WaitSet(void){
	wait_time=timeGetTime();
}
//----------[ ウェイト ]------------------------------------------------------------------------
void Wait(DWORD msec){
	MSG msg;
	do{
		while(PeekMessage(&amp;msg,NULL,0,0,PM_NOREMOVE)){
			if(!GetMessage(&amp;msg,NULL,0,0))
				Quit();
			TranslateMessage(&amp;msg);
			DispatchMessage(&amp;msg);
		}
	}while(timeGetTime()&lt;wait_time+msec);
}
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	&lt;略&gt;

	int cx=320-16,cy=240-16,mv=3;
	//メインループ
	while(1){
		WaitSet();			//現在の時間（単位：ミリ秒）取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,&quot;カーソルキーで移動 pauseキーで終了&quot;,255);

		//キャラクター移動
		if(keyg[38])	//↑
			cy-=mv;
		else
		if(keyg[40])	//↓
			cy+=mv;
		else
		if(keyg[37])	//←
			cx-=mv;
		else
		if(keyg[39])	//→
			cx+=mv;

		//キャラクタ表示
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();					//フィリップ
		Wait(1000/60);				//メッセージループへ
	}
</pre>
<p>さて、まずメインループの最初でWaitSet()関数を呼び出しています。<br />
これは自作関数なのでコードを見てもらえば解ると思いますが、グローバル変数wait_timeに、timeGetTime()関数を呼び出し、現在の時間をミリ秒（1秒=1000ミリ秒）で保存しておきます。<br />
その後、メインループでいろいろな処理をし、最後にWait()関数が呼ばれています。<br />
これも自作関数です。<br />
このWait()関数の内容は主にメッセージループなのですが、現在の時間（timeGetTime()）が、最初に記憶しておいたwait_time+Wait()関数の引数msecよりも小さい間はひたすたメッセージループを回しています。<br />
つまり、WaitSet()を呼び出した時間から、Wait()の引数msecミリ秒時間が経つまでWait()関数内で止まっている事になります。</p>
<p>Wait()の引数を1000にすれば、秒間1回更新、500で2回更新・・・。<br />
1000/nでn回更新となります。<br />
ただ、この方法にも欠点があるのですが、それはWaitSet()を呼び出してから、いろいろな処理をし、Wait(DWORD msec);を呼ぶ場合、「いろいろな処理」の時間がWaitの引数「msec」より大きくなってしまうと、<br />
Wait()関数内は、ほぼ素通り（1回はメッセージループの処理をやります）になり、いわゆる「もたつき」だとか「処理落ち」と言った現象になります。<br />
普通、ゲームでは秒間30回更新（1ループ33msec）を目安にしたほうがいいですね。<br />
これより小さいと、動作がカクカクに見えてしまうし、大きいと、処理落ちする可能性が大きくなります。<br />
まぁ場合によりますので、いろいろ試してみてください（＾＾；</p>
<h3>同期のお話 オマケ</h3>
<p>さて、上では疑似タイマー処理なるものを紹介しましたが、<br />
タイマー系の処理の種類はいろいろあります。<br />
今回紹介したものは、毎回処理される時間を一定時間に合わせていますね？<br />
この考え方は、よく固定フレームとか言います。<br />
固定フレームでは、プログラムが簡単になる分、「もたつき」が起こりうる可能性が出てしまいます。<br />
また、例えば秒間30回更新（30fps）に設定した場合、とても高性能なマシンで動かしても30FPSです。<br />
ちょっとCPUがもったいない気もしますよね？<br />
まぁ、60FPS設定にしておいて、処理がおいつかない場合は、描画をスキップするなんて事もよくやるようですが・・・。</p>
<p>ちなみに固定フレームに対するものは、変動フレームです。<br />
これは、前回から今回のループに掛かった時間を計測しておき、<br />
「距離＝時間×速度」と言ったような式を用いてゲームを進行させる方法です。<br />
この方法では、プログラムが複雑になったり、浮動小数点を使用する必要があったりしますが、マシンが高性能であればある程、ゲームが滑らかになります。</p>
<p>固定か変動かはケースバイケースなので、ゲームに合った方を使用してください。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213142634.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>第1回　キー入力処理</title>
		<link>http://takabosoft.com/19991213134924.html</link>
		<comments>http://takabosoft.com/19991213134924.html#comments</comments>
		<pubDate>Mon, 13 Dec 1999 04:49:24 +0000</pubDate>
		<dc:creator>takabosoft</dc:creator>
				<category><![CDATA[DirectXゲーム作成講座　ゲーム制作基礎編]]></category>

		<guid isPermaLink="false">http://takabosoft.com/19991213134924.html</guid>
		<description><![CDATA[サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（ [...]]]></description>
			<content:encoded><![CDATA[<h3>サンプルコードのダウンロード</h3>
<p>では、まず<a href="http://takabosoft.com/wp-content/uploads/1999/12/game_01.lzh"><b>サンプルコード</b></a>をダウンロードし、解凍してください。<br />
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください（「game_01.dsw」をダブルクリックすれば開けます）。<br />
圧縮ファイルに含まれる「game_01.exe」をダブルクリックし、実行してみてください。<br />
どうでしょう？画面が切り替わり、フルスクリーン化すると思います。<br />
そして、何かキーを押すと、そのキーコードが画面に表示されると思います。<br />
というわけで今回は、キー入力について解説したいと思います。</p>
<h3>ウィンドウメッセージ</h3>
<p>前にDirectDraw講座でも触れましたが、Windows上ではアプリケーションとWindows（OS）がコミュニケーションをとるために、ウィンドウメッセージというのをやりくりします。</p>
<p>例えばキーが押されたらWindowsがアプリケーションに対して「キーが押されたぞ」というメッセージを送信します。<br />
そしたらアプリ側はメッセージループ中、そのメッセージを受信し、ウィンドウプロシージャと呼ばれる関数を呼び出します（ウィンドウプロシージャは自分で制作する事になります）。</p>
<p>ではサンプルコードを見ていきます。</p>
<pre class="brush: cpp;">

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){

	switch(iMessage){

	略

	//キー押された
	case WM_KEYDOWN:
		//pauseキー(キーコード19）が押されたら終了
		if(wParam==19)
			Quit();

		ClearScreen(lpBack);
		DdTextOut(lpBack,0,0,&quot;Pause(STOP)キーで終了&quot;,1);
		sprintf(tmp,&quot;key=%d&quot;,wParam);
		DdTextOut(lpBack,0,16,tmp,1);
		Flip();

	return(TRUE);
</pre>
<p>メッセージの種類は「UINT iMessage」に格納されています。<br />
ですから、switch文で分岐してやります。<br />
キーが押された時の値は「WM_KEYDOWN」（マクロ）です。<br />
そして、wParamに、押されたキーのキーコードが格納されています。<br />
このキーコードについては、資料不足で（爆）どの値がどのキーかは判りません。<br />
ですが、キーが押されたら、そのキーコードを画面に表示する事で、キーのキーコードが判るというわけです。</p>
]]></content:encoded>
			<wfw:commentRss>http://takabosoft.com/19991213134924.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

