ページ 11

マップのスクロールのさせ方について

Posted: 2013年1月29日(火) 07:30
by taketoshi
今、マップをスクロールさせる処理について考えています。

640×480の画面に対し、800×480のマップ画面を用意して
キャラクタが動いたら横スクロールさせていきたいです。

試しに画面の中央320からキャラクタの移動した距離でスクロール量を算出してみたのですが
スクロール+キャラクタの移動で2歩ずつ歩いてしまいます。

スクロール量が1以上の時は、キャラクタの位置を補正すればいいのか?と考えましたが
キャラクタの位置からスクロール量を算出しているため上手くいきませんでした。
最初はx方向のスクロールを行い次にy方向のスクロールも実施してみたいです。

マップスクロールの考え方についてご指導をお願いします
► スポイラーを表示

Re: マップのスクロールのさせ方について

Posted: 2013年1月29日(火) 07:58
by Priest
165行目の

コード:

DrawGraph(player.x,player.y,player.nCharDrawHandle,true);
を、

コード:

DrawGraph(player.x-32*nScroll,player.y,player.nCharDrawHandle,true);
に変えるのではダメですか?
taketoshi さんが書きました:試しに画面の中央320からキャラクタの移動した距離でスクロール量を算出してみたのですがスクロール+キャラクタの移動で2歩ずつ歩いてしまいます。
のでしたら、キャラクタをその分戻してあげればいいのではと思ったので。

【追記】
taketoshi さんが書きました: スクロール量が1以上の時は、キャラクタの位置を補正すればいいのか?と考えましたが
の、"キャラクタ位置"はplayer.xのような、そのゲームのキャラクタそのものの位置を指していますか?
もしそうでしたら、そこが問題となっています。

マップスクロールを実装することで、キャラクタの本来の位置とスクリーン上の位置にずれが生じます。
マップスクロールがない環境では、それらの位置は同じになりますので、意識しなくてもよかったのですが…。

なので、キャラクタの位置とスクリーンのスクロール量からキャラクタのスクリーン上の位置を計算する必要が出てきます。

コード:

スクリーン上の位置.x = キャラクタ本来の位置.x - スクロール量.x;
スクリーン上の位置.y = キャラクタ本来の位置.y - スクロール量.y;
となります。

構造体にスクリーン上の位置player.sx,player.syを追加し、適切なところでそれを計算させて、

コード:

DrawGraph(player.sx,player.sy,player.nCharDrawHandle,true);
とした方が、意識するようにもなっていいかもしれません。

【さらに追記】

コード:

        //スクロールデータの計算(仮定義)//
        int nChar = WindowSize / 2;//キャラ中央位置
        if((player.x - nChar) / 32 == 1) nScroll = 1;
        else if((player.x - nChar) / 32 == 2) nScroll = 2;
        else if((player.x - nChar) / 32 == 3) nScroll = 3;
        else if((player.x - nChar) / 32 == 4) nScroll = 4;
        else if((player.x - nChar) / 32 == 5) nScroll = 5;
        else if((player.x - nChar) /32 == 0) nScroll = 0;
を、

コード:

         //スクロールデータの計算(仮定義)//
        if(player.x>WindowSize / 2) nScroll = player.x - WindowSize / 2;
        else nScroll=0;
のようにして、(スクロール量の表現をキャラクタの座標と同じスケールにして)
全体を調整すればもっと滑らかなスクロールになりますよ!
今の状態ではスクロールがカクカクだと思いますので。

Re: マップのスクロールのさせ方について

Posted: 2013年1月29日(火) 11:36
by softya(ソフト屋)
一番シンプルにする方法は、キャラクタとマップの座標を一致させることです。これをマップ座標とします。
主人公キャラクタの現在の座標情報から表示する画面上の座標を随時計算します。これを画面座標とします。
つまり、主人公キャラクタとの相対位置で全ての画面座標を計算するのです。

主人公キャラクタの画面座標をセンターに置くとして次の式でマップの表示を開始する位置を計算出来ます。
表示マップの右上座標 = キャラクタのマップ座標 - (画面サイズ / 2 )

こうするとスクロールするというのを意識しなくでもマップ座標上をキャラクタが動くことで自動的にスクロールしてくれることになります。

過去の参考ログです。
「マップスクロール時、敵の行動について • C言語交流フォーラム ~ mixC++ ~」
http://dixq.net/forum/viewtopic.php?f=3&t=10522
「マップスクロール • C言語交流フォーラム ~ mixC++ ~」
http://dixq.net/forum/viewtopic.php?f=3&t=8773#p71462

Re: マップのスクロールのさせ方について

Posted: 2013年1月29日(火) 18:42
by ISLe
ある程度端に寄ったらスクロールするようにしたいのであれば、マップ座標を画面座標とスクロール座標に分解して計算する必要があります。

とりあえず描画に関するコードを後半にまとめましょう。


わたしのブログの記事でJavaですが、参考になると思います。
タイルマップ座標のオフセットを求める:ISLeのビデオゲーム工房
【JavaSE】フレームワークのタイルマップ実装:ISLeのビデオゲーム工房
このプログラムは画面に見えている分だけを描画するようにもなっています。

Re: マップのスクロールのさせ方について

Posted: 2013年1月30日(水) 00:54
by ISLe
スクロール以外に気になる箇所があったのでほどほどに書き換え、スクロール処理も加えました。

画面の幅の1/3よりも端に近付くとスクロールするようにしました。

スクロール処理のポイントとしては、
1. スクロール座標はカメラ位置。独立して管理する。
2. 基本はマップ座標(ワールド座標)。必要に応じて表示座標を求め、しっかり区別して適切に使用する。
といったところです。

コード:

#include "DxLib.h"

#define WindowSize 640

//キャラクターデータ
typedef struct Character {
	int x;
	int y;
	int image[16];
	bool bWalkFlag;
	int nCharDirection;//向き
	int nCharDrawHandle;//画像
} CharData;

//マップ定義
int Block[15][25] = {4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,2,3,0,2,0,2,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,
					 10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9
					};

int CheckWalk(CharData cd) {

	//上向き
	if(cd.nCharDirection == 0) {
		if(Block[(cd.y / 32 - 1)][cd.x / 32] != 0)return 1;
	}
	//下向き
	if(cd.nCharDirection == 2) {
		if(Block[(cd.y / 32 + 1)][cd.x / 32] != 0)return 1;
	}
	//右向き
	if(cd.nCharDirection == 3) {
		if(Block[(cd.y / 32)][(cd.x/32+1)] != 0) return 1;
	}
	//左向き
	if(cd.nCharDirection == 1) {
		if(Block[(cd.y / 32)][(cd.x/32-1)] != 0 ) return 1;
	}
	return 0;

}

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) {
	ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

	CharData player;
	char Key[256];
	const char *lpCharImage ="画像/char01.png";
	const char *lpMapImage ="画像/maptip.png";
	const char *lpMapImage2 ="画像/map2.png";
	const char *lpMapImage3 ="画像/map3.png";

	int nMapHandle[12];
	nMapHandle[2] = LoadGraph(lpMapImage2, TRUE);
	nMapHandle[3] = LoadGraph(lpMapImage3, TRUE);

	LoadDivGraph(lpMapImage,9,3,3,32,32,&nMapHandle[4],TRUE);
	LoadDivGraph(lpCharImage,16,4,4,32,32,player.image,0);
	player.x=320;
	player.y=32;
	player.nCharDirection = 0;

	int nScroll_x = 0;

	const int BLOCK_HEIGHT = sizeof(Block)/sizeof(Block[0]);//行数の取得
	const int BLOCK_WIDTH = sizeof(Block[0]) / sizeof(Block[0][0]);//列数の取得
	const int WORLD_HEIGHT = BLOCK_HEIGHT * 32;
	const int WORLD_WIDTH = BLOCK_WIDTH * 32;

	while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ) {
		GetHitKeyStateAll( Key ) ;

		//キャラ移動入力
		if(player.x % 32 == 0 && player.y %32 == 0) {
			if(Key[KEY_INPUT_UP] == 1) { //上
				player.nCharDirection = 0;
				player.bWalkFlag = true;
			}
			else if(Key[KEY_INPUT_DOWN] == 1) {
				player.nCharDirection = 2;//下
				player.bWalkFlag = true;
			}
			else if(Key[KEY_INPUT_RIGHT] == 1) {
				player.nCharDirection = 3;//右
				player.bWalkFlag = true;
			}
			else if(Key[KEY_INPUT_LEFT] == 1) {
				player.nCharDirection = 1;//左
				player.bWalkFlag = true;
			} else {
				player.bWalkFlag = false;
			}
			if(player.bWalkFlag == true) {
				if(CheckWalk(player) == 1) player.bWalkFlag = false;
			}
		}
		if (player.bWalkFlag) {
			if(player.nCharDirection == 0) player.y--;//上
			else if(player.nCharDirection == 2) player.y++;//下
			else if(player.nCharDirection == 3) player.x++;//右
			else if(player.nCharDirection == 1) player.x--;//左
		}

		/*
		 * スクロール座標の更新
		 */

		// プレイヤーの画面座標を求める
		int player_screen_x = player.x - nScroll_x;

		int scroll_distance_x; // スクロール量

		if ((scroll_distance_x = player_screen_x - (WindowSize / 3)) < 0) {
			// プレイヤーが画面の左1/3に入ったら押し戻すようにスクロールする
			if (nScroll_x + scroll_distance_x < 0) {
				// 端を超えないようにスクロール量補正
				scroll_distance_x = -nScroll_x;
			}
		}
		else if ((scroll_distance_x = player_screen_x - (WindowSize * 2 / 3)) > 0) {
			// プレイヤーが画面の右1/3に入ったら押し戻すようにスクロールする
			if (nScroll_x + scroll_distance_x > WORLD_WIDTH - WindowSize) {
				// 端を超えないようにスクロール量補正
				scroll_distance_x = (WORLD_WIDTH - WindowSize) - nScroll_x;
			}
		}
		else {
			// スクロールしない
			scroll_distance_x = 0;
		}

		nScroll_x += scroll_distance_x; // スクロール座標を更新
		player_screen_x -= scroll_distance_x; // 合わせて主人公の表示位置を調整

		/*
		 * マップの描画
		 */

		for (int y = 0; y < BLOCK_HEIGHT; ++y) {
			for (int x = 0; x < BLOCK_WIDTH; ++x) {
				DrawGraph(x*32 - nScroll_x, y*32, nMapHandle[4+4], TRUE);
				if (Block[y][x] >= 2 && Block[y][x] <= 11) {
					const int tileidx[12] = { -1, -1, 2, 3, 0+4, 1+4, 2+4, 3+4, 7+4, 8+4, 6+4, 5+4 };
					DrawGraph(x*32 - nScroll_x, y*32, nMapHandle[tileidx[Block[y][x]]], TRUE);
				}
			}
		}


		int player_image_index = (player.x % 32 + player.y % 32) / 8 + player.nCharDirection * 4;
		player.nCharDrawHandle = player.image[player_image_index];

		DrawFormatString(500, 0, GetColor(255,255,255), "スクロール量 = %d", nScroll_x);

		DrawGraph(player_screen_x, player.y, player.nCharDrawHandle, TRUE);

		DrawFormatString(0, 0, GetColor(255,255,255), "座標 X = %d 座標 Y = %d", player.x, player.y);
		DrawFormatString(300, 0, GetColor(255,255,255), "画像データ = %d", player_image_index);
	}
	DxLib_End(); // DXライブラリ終了処理

	return 0;
}

Re: マップのスクロールのさせ方について

Posted: 2013年2月03日(日) 11:28
by taketoshi
皆さん返信ありがとうございます。
仕事が忙しく亀レスになって申し訳ないです。

ISLeさんは見本コードまで書いていただいてありがとうございました。とても参考になりました。
教えていただいた情報やコードを基に自分で考えて以下のように書き直し、上手く動作することを確認しました。

実際のキャラクタの位置とスクリーン座標の関係について知識として取り込むことができたので
次はY座標の移動を実装していきます。
► スポイラーを表示
ISLeさんのコードの画像描写の仕方がとてもエレガントだと思うので、時間をかけて考えてみます。
スクロール方法は理解できたので解決にさせていただきます。ありがとうございました。

Re: マップのスクロールのさせ方について

Posted: 2013年2月03日(日) 18:44
by ISLe
わたしのコードには「押し戻すようにスクロールする」と回りくどい表現のコメントを書きました。
これには、スクロールが少し遅れて始まって徐々に追い付くようにしたりとか、主人公が高速移動するときには前方を広く取るようにしたりとかに簡単に対応できるようにとの意図があります。

主人公の座標とスクロールのオフセットを一致させるだけならこのような処理はなくても良いのですが、スクロールを独立させたほうが応用範囲が広くなるのでそうしました。
#というより処理を分けることが身に染み付いてしまっている。
例えばムービーシーンで主人公と関係なくスクロールさせたいときなどは主人公の座標と同期を取る処理は邪魔になります。

将来必要になったときに思い出していただけたら幸いです。

Re: マップのスクロールのさせ方について

Posted: 2013年2月03日(日) 21:01
by taketoshi
>ISLeさん

確かに、主人公を基点としないオートスクロールは、私の処理では実装できそうにないです。

勉強になります。
解説ありがとうございます。