ページ 11

複数のソースコードファイルにプログラムを書く

Posted: 2014年11月04日(火) 12:26
by free
タイトルとゲームオーバ画面を表示するプログラムという記事は覚えていますでしょうか?あの続きです。

あれから本の内容を見ながら進めていたのですが、またまた躓いてしまいました。
(いつもの事ながら申し訳ありません)
サンプルファイルありますけど、すでに完成版で見てもさっぱりわかりません。

今回、何がしたいのかという概要を下に書いておきます。
(前)
  • 読み込んだ画像を動かしてみたり、リアルタイムでプログラムを進行させてみる
  • タイトル、ゲームオーバー画面を表示してみる(ここが前の質問)
(今)
(前)で作ったプログラムを使って、ゲームを作成するというもの。

・複製の作業
(前)プロジェクトをコピーし、複製されたファイルの名前を変える(
プロジェクト内のプロパティの名前、ルート名前空間の変更、
前の<Debug>、.sdf、.vcxproj.userファイルを削除、
.vcxprojと.vcxproj.filtersファイルの名前を変える)

ゲーム作成のために、
  • cppファイルを新たに作成し、その中に関数の定義を書く。
  • ヘッダファイルを作成し、その中に関数のプロトタイプ宣言を書く。
  • 後はヘッダファイルをインクルードして関数を呼び出す。
WinMain関数を含むmain.cppと主人公キャラクターの処理とマップ表示などゲームの主要な処理を行う部分とで分けます。

Gamemain.hのプログラム

コード:

#ifndef __GAMEMAIN_H__
#define __GAMEMAIN_H__

#include <DxLib.h>
#include "Main.h"

void GameMain();

#endif
Main.hのプログラム

コード:

#ifndef __MAIN_H__
#define __MAIN_H__

#include <DxLib.h>
#include "Gamemain.h"

//グローバル変数
//時間計測用変数
extern int g_lasttime = 0;		//直前の計測時間
extern float g_fremetime = 0;		//1ループにかかった時間
extern int g_timerstart;		//タイマー用変数

enum GameState{				//ゲーム状態
	GAME_TITLE, GAME_MAIN,
	GAME_CLEAR, GAME_OVER
};
extern GameState g_gamestate;
extern int g_gametitleimg;		//タイトルイメージ
extern int g_heroimg;		//画像
extern float g_hx = 0, g_hy = 0;		//座標

//ボタン
extern BOOL g_akey_prev;		//直前のAボタンの状態

//フォント
extern int g_middlefont;		//中サイズのフォントハンドル
extern int g_largefont;		//大サイズのフォントハンドル

//関数プロトタイプ宣言
void DrawGameTitle();
void DrawGameMain();
void DrawGameClear();
void DrawGameOver();
BOOL IsAkeyTrigger(int key);

#endif
Main.cppのプログラム

コード:

#include "Main.h"

//グローバル変数
//時間計測用変数
int g_timerstart;		//タイマー用変数

GameState g_gamestate = GAME_TITLE;

int g_gametitleimg;		//タイトルイメージ
int g_heroimg;		//画像

//ボタン
BOOL g_akey_prev;		//直前のAボタンの状態

//フォント
int g_middlefont;		//中サイズのフォントハンドル
int g_largefont;		//大サイズのフォントハンドル

int WINAPI WinMain(HINSTANCE h1, HINSTANCE hP, LPSTR lPC, int nC){
	//ウィンドウモードにする
	ChangeWindowMode(TRUE);
	//ウィンドウサイズを変更する
	SetGraphMode(800, 600, 32);
	//DXライブラリ初期化
	if (DxLib_Init() == -1) return -1;
	
	//画像を読み込み
	g_gametitleimg = LoadGraph("media\\smp1_title.png");
	g_heroimg = LoadGraph("media\\smp1_chara01.png");
	g_middlefont = CreateFontToHandle("メイリオ", 42, -1, DX_FONTTYPE_ANTIALIASING);
	g_largefont = CreateFontToHandle("メイリオ", 90, -1, DX_FONTTYPE_ANTIALIASING);
	
	SetDrawScreen(DX_SCREEN_BACK);
	g_lasttime = GetNowCount(); //現在時刻の記録
	while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0){
		//1ループにかかった時間を計測
		int curtime = GetNowCount();
		g_fremetime = (float)(curtime - g_lasttime) / 1000.0f;
		g_lasttime = curtime;

		ClearDrawScreen();
		//画面描画関数に切り替え
		switch (g_gamestate)
		{
		case GAME_TITLE:
			DrawGameTitle();
			break;
		case GAME_MAIN:
			DrawGameMain();
			break;
		case GAME_CLEAR:
			DrawGameClear();
			break;
		case GAME_OVER:
			DrawGameOver();
			break;
		default:
			break;
		}
		ScreenFlip();
	}

	//待機
	WaitKey();
	//DXライブラリの終了処理
	DxLib_End();
	return 0;
}

//タイトル画面描画
void DrawGameTitle(){
	DrawBox(0, 0, 800, 600, GetColor(255, 255, 255), TRUE);
	DrawGraph(0, 0, g_gametitleimg, TRUE);
	//テキスト表示
	DrawStringToHandle(100, 400, "Zキーでゲームスタート",
		GetColor(255, 0, 255), g_middlefont);
	DrawStringToHandle(100, 460, "カーソルキーで上下左右に移動",
		GetColor(0, 0, 0), g_middlefont);
	//キーをチェックして画面を切り替え
	int key = GetJoypadInputState(DX_INPUT_KEY_PAD1);
	if (IsAkeyTrigger(key) == TRUE) g_gamestate = GAME_MAIN;
}

//ゲーム本編描画
void DrawGameMain(){
	GameMain();
}

//ゲームクリア画面描画
void DrawGameClear(){

}

//ゲームオーバー画面描画
void DrawGameOver(){
	//テキスト表示
	DrawStringToHandle(100, 200, "ゲームオーバー",
		GetColor(255, 0, 0), g_largefont);
	//5秒経ったらタイトル画面へ
	if (g_lasttime - g_timerstart > 5000) g_gamestate = GAME_TITLE;
}

//キートリガー処理
BOOL IsAkeyTrigger(int key){
	if (key & PAD_INPUT_A){
		if (g_akey_prev == FALSE){
			g_akey_prev = TRUE;
			return TRUE;
		}
	}
	else{
		g_akey_prev = FALSE;
	}
	return FALSE;
}
Gamemain.cppのプログラム

コード:

#include "Gamemain.h"

void GameMain(){
	//自キャラ移動
	int Key = GetJoypadInputState(DX_INPUT_KEY_PAD1);
	float mv = 80.0f * g_fremetime; //移動量計算
	if (Key & PAD_INPUT_UP)		g_hy -= mv;
	if (Key & PAD_INPUT_DOWN)   g_hy += mv;
	if (Key & PAD_INPUT_LEFT)   g_hx -= mv;
	if (Key & PAD_INPUT_RIGHT)  g_hx += mv;
	DrawGraph(g_hx, g_hy, g_heroimg, TRUE);
	//Zキーをチェックして画面を切り替え
	if (IsAkeyTrigger(Key) == TRUE){
		g_gamestate = GAME_OVER;
		g_timerstart = g_lasttime;		//タイマーセット
	}
}
※プログラムファイル名はサンプルと区別するために最初の文字を大文字にしてあります。

エラー
1 error LNK2005: "int g_lasttime" (?g_lasttime@@3HA) は既に Gamemain.obj で定義されています。
2 error LNK2005: "float g_fremetime" (?g_fremetime@@3MA) は既に Gamemain.obj で定義されています。
3 error LNK2005: "float g_hx" (?g_hx@@3MA) は既に Gamemain.obj で定義されています。
4 error LNK2005: "float g_hy" (?g_hy@@3MA) は既に Gamemain.obj で定義されています。
5 error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

元のMain.cppにあったコードをGamemainにペーストし、Main.hにインクルードと列挙体、関数プロトタイプを持ってくる。
(前にexternをつける)Main.cppでMain.hに書いた部分を削除し、代わりにMain.hをインクルード。
インクルードの繰り返しを防ぐために#ifndefと#endifをヘッダファイル内に書く。

・・・で、コンパイルをしてみたら上のエラーが出てしまいました。
本に従ってプログラムしたのに、なぜエラーになるのでしょうか?(本にはこのエラーの対処は載っていません)
いつもの様な一文字だけ間違ってるっていうのは無いですよね?それとも、消すところを間違えているのでしょうか?




       

Re: 複数のソースコードファイルにプログラムを書く

Posted: 2014年11月04日(火) 12:37
by h2so5
externと変数の初期化は両立できません。
extern修飾されたグローバル変数は初期化せずに、関数内で初期化するようにしてください。
cppに実体を定義するのを忘れずに。

Re: 複数のソースコードファイルにプログラムを書く

Posted: 2014年11月04日(火) 13:34
by softya(ソフト屋)
ヘッダファイルは無くても実は書けるんで、externの意味と変数の実体を理解するためにテストのコードを書いて色々試してみたほうが良いと思います。
ヘッダファイルは便利だから使うのであって必然ではないです。

Re: 複数のソースコードファイルにプログラムを書く

Posted: 2014年11月04日(火) 19:33
by free
>>h2so5さん
Main.hのプログラム

コード:

#ifndef __MAIN_H__
#define __MAIN_H__

#include <DxLib.h>
#include "Gamemain.h"

//グローバル変数
//時間計測用変数
extern int g_lasttime;		//直前の計測時間
extern float g_fremetime;		//1ループにかかった時間
extern int g_timerstart;		//タイマー用変数

enum GameState{				//ゲーム状態
	GAME_TITLE, GAME_MAIN,
	GAME_CLEAR, GAME_OVER
};
extern GameState g_gamestate;
extern int g_gametitleimg;		//タイトルイメージ
extern int g_heroimg;		//画像
extern float g_hx, g_hy;		//座標

//ボタン
extern BOOL g_akey_prev;		//直前のAボタンの状態

//フォント
extern int g_middlefont;		//中サイズのフォントハンドル
extern int g_largefont;		//大サイズのフォントハンドル

//関数プロトタイプ宣言
void DrawGameTitle();
void DrawGameMain();
void DrawGameClear();
void DrawGameOver();
BOOL IsAkeyTrigger(int key);

#endif
Main.cppのプログラム(最初の19行まで)

コード:

#include "Main.h"

//グローバル変数
//時間計測用変数
int g_lasttime;		//直前の計測時間
float g_fremetime;		//1ループにかかった時間
int g_timerstart;		//タイマー用変数

GameState g_gamestate;
int g_gametitleimg;		//タイトルイメージ
int g_heroimg;		//画像
float g_hx, g_hy;		//座標

//ボタン
BOOL g_akey_prev;		//直前のAボタンの状態

//フォント
int g_middlefont;		//中サイズのフォントハンドル
int g_largefont;		//大サイズのフォントハンドル
どうやら、extern文の終わりに"=0"が付いていたというのと、
Main.cppでも"=0"と付いていたのが原因だったようです。
h2so5さんの言ってた「関数内で初期化」「cppに実体を定義する」ってこういうことでしょうか?