ファイル分割について

main関数以外の関数を自作できるというのはC言語講座で学んだ通りだろう。
だが、全ての関数を1つのファイルに書いていたらどこに何を書いたか分からなくなり、後からの修正が非常に困難となる。
そこで、こちらでは必要に応じてファイルを分割する方法について説明する。

新規ファイルの作成方法

ソリューションエクスプローラーのフォルダ名を右クリック(デフォルトでは「ソースファイル」と「ヘッダーファイル」があるはず)。 「追加」 → 「新しい項目」の順に選択しクリック。
C++ファイルかヘッダーファイルを選択し、ファイル名を入力しウィンドウ右下の「追加」をクリック。

ヘッダー(.h)ファイル

こちらにmain関数以外の関数やそれに関する変数を書き、それをmain関数のあるソースコードにインクルードすることによって、項目に応じてソースコードを分割する。
プレイヤーに関するものはplayer.h、敵に関するものはenemy.hといった具合だ。

key.h

char buf[256] = { 0 };
int keystate[256] = { 0 };

//キー入力状態を更新する関数
void keyupdate()
{
    GetHitKeyStateAll(buf);
    for (int i = 0; i< 256; i++)
    {
        if (buf[i] == 1) keystate[i]++;
        else keystate[i] = 0;
    }
}

ヘッダーファイルをインクルードするには、インクルードしたいファイルのコードの最初に#include "ヘッダー名.h"と書く。

main.cpp

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

int WINAPI WinMain( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow )
{
    ChangeWindowMode( TRUE );//非全画面にセット
    SetGraphMode( 800 , 600 , 32 );//画面サイズ指定
    SetOutApplicationLogValidFlag( FALSE ) ;//Log.txtを生成しないように設定
    if(DxLib_Init() == 1){return -1;}//初期化に失敗時にエラーを吐かせて終了

    //
    //ここで敵やプレイヤーのオブジェクトの実体を作る
    //
    int px = 400, py = 300;

    while( ProcessMessage()==0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        keyupdate();

        ClearDrawScreen();//裏画面消す
        SetDrawScreen(DX_SCREEN_BACK);//描画先を裏画面に

        if (keystate[KEY_INPUT_LEFT] >= 1)
        {
            px -= 8;
        }
        if (keystate[KEY_INPUT_RIGHT] >= 1)
        {
            px += 8;
        }

        DrawCircle(px, py, 16, GetColor(0, 0, 255), TRUE);

        ScreenFlip();
    }

    DxLib_End();
    return 0;
}

C++(.cpp)ファイル

ヘッダーファイルには関数の宣言のみ記入(プロトタイプ宣言という)し、実装部はC++ファイルに書くということも可能だ。
その際、実装部を記入するC++ファイルにもヘッダーファイルをインクルードする。ファイル名はプロトタイプ宣言を記入したヘッダーファイルと同名にするのが望ましい。

key.h

#include "DxLib.h"

//キー取得用の配列
//複数のファイルでインクルードするなどして複数のファイルで使うことになる変数にはexternをつける
extern char buf[256];
extern int keystate[256];

//キー入力状態を更新する関数
void keyupdate();
key.cpp

#include "key.h"

//externを付けた変数は改めて宣言する必要がある
char buf[256] = { 0 };
int keystate[256] = { 0 };

//キー入力状態を更新する関数
void keyupdate()
{
    GetHitKeyStateAll(buf);
    for (int i = 0; i< 256; i++)
    {
        if (buf[i] == 1) keystate[i]++;
        else keystate[i] = 0;
    }
}

main.cpp

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

int WINAPI WinMain( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow )
{
    ChangeWindowMode( TRUE );//非全画面にセット
    SetGraphMode( 800 , 600 , 32 );//画面サイズ指定
    SetOutApplicationLogValidFlag( FALSE ) ;//Log.txtを生成しないように設定
    if(DxLib_Init() == 1){return -1;}//初期化に失敗時にエラーを吐かせて終了

    //
    //ここで敵やプレイヤーのオブジェクトの実体を作る
    //
    int px = 400, py = 300;

    while( ProcessMessage()==0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        keyupdate();

        ClearDrawScreen();//裏画面消す
        SetDrawScreen(DX_SCREEN_BACK);//描画先を裏画面に

        if (keystate[KEY_INPUT_LEFT] >= 1)
        {
            px -= 8;
        }
        if (keystate[KEY_INPUT_RIGHT] >= 1)
        {
            px += 8;
        }

        DrawCircle(px, py, 16, GetColor(0, 0, 255), TRUE);

        ScreenFlip();
    }

    DxLib_End();
    return 0;
}

ソースコードは上の行から読み込まれ、その時点でコンパイラが読み込んでいない関数や変数にはアクセスできないというのは聞いたことがあるだろうか。
プロトタイプ宣言は、この問題を解決するために関数の存在のみをコンパイラに伝えるための手段である。
関数の存在をコンパイラに伝えてさえいれば、実装部は関数を呼び出すよりも後に書いてよいのだ。

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

int px = 400, py = 584;
int ex = 400, ey = 300, eflag = 0;

//プロトタイプ宣言。これをコメントアウトするとエラーに
void EnemyMove();

void PlayerMove()
{
    //キーを押すと移動する
    if (keystate[KEY_INPUT_LEFT] >= 1)
    {
        px -= 8;
    }
    if (keystate[KEY_INPUT_RIGHT] >= 1)
    {
        px += 8;
    }

    //Zキーを押している間のみ敵が移動する
    if (keystate[KEY_INPUT_Z] >= 1)
    {
        EnemyMove();
    }

    //画面外へ出ないようにする
    if (px < 16)
    {
        px = 16;
    }
    if (px > 784)
    {
        px = 784;
    }
}

int WINAPI WinMain( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow )
{
    ChangeWindowMode( TRUE );//非全画面にセット
    SetGraphMode( 800 , 600 , 32 );//画面サイズ指定
    SetOutApplicationLogValidFlag( FALSE ) ;//Log.txtを生成しないように設定
    if(DxLib_Init() == 1){return -1;}//初期化に失敗時にエラーを吐かせて終了

    //
    //ここで敵やプレイヤーのオブジェクトの実体を作る
    //

    while( ProcessMessage()==0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        keyupdate();

        ClearDrawScreen();//裏画面消す       
        SetDrawScreen(DX_SCREEN_BACK);//描画先を裏画面に

        PlayerMove();

        //自機描画
        DrawCircle(px, py, 16, GetColor(0, 0, 255), TRUE);
        //敵描画
        DrawCircle(ex, ey, 16, GetColor(255, 0, 0), TRUE);

        ScreenFlip();
    }

    DxLib_End();
    return 0;
}

void EnemyMove()
{
    //移動する
    if (eflag == 0)
    {
        ex += 4;
    }
    else
    {
        ex -= 4;
    }

    //画面端で跳ね返る
    if (ex < 0)
    {
        eflag = 0;
    }
    if (ex > 800)
    {
        eflag = 1;
    }
}

ファイルを分割した場合には、プロトタイプ宣言をヘッダーファイルに記入し、実装部をC++ファイルに記入することで、上記と同じように問題を解決することができる。player.cppでenemy.hに記入した変数や関数を利用したくなったら、そちらをインクルードすればよい。

key.h

#pragma once
#include "DxLib.h"

//キー取得用の配列
//複数のファイルでインクルードするなどして複数のファイルで使うことになる変数にはexternをつける
extern char buf[256];
extern int keystate[256];

//キー入力状態を更新する関数
void keyupdate();

key.cpp

#include "key.h"

//externを付けた変数は改めて宣言する必要がある
char buf[256] = { 0 };
int keystate[256] = { 0 };

//キー入力状態を更新する関数
void keyupdate()
{
    GetHitKeyStateAll(buf);
    for (int i = 0; i< 256; i++)
    {
        if (buf[i] == 1) keystate[i]++;
        else keystate[i] = 0;
    }
}

player.h

//複数のファイルでインクルードするなどして複数のファイルで使うことになる変数にはexternをつける
extern int px, py;

void PlayerMove();

player.cpp

#include "DxLib.h"
#include "key.h"
#include "player.h"
#include "enemy.h"

//externを付けた変数は改めて宣言する必要がある
int px = 400, py = 584;

void PlayerMove()
{
    //キーを押すと移動する
    if (keystate[KEY_INPUT_LEFT] >= 1)
    {
        px -= 8;
    }
    if (keystate[KEY_INPUT_RIGHT] >= 1)
    {
        px += 8;
    }

    //Zキーで敵の真下に移動する
    //enemy.hをインクルードしたことで、変数exにアクセスすることが可能となった
    if (keystate[KEY_INPUT_Z] == 1)
    {
        px = ex;
    }

    //画面外へ出ないようにする
    if (px < 16)
    {
        px = 16;
    }
    if (px > 784)
    {
        px = 784;
    }

    //画像を描画する関数
    //本来は画像描画用の関数を別に用意し、そちらに書くべきとされている
    DrawCircle(px, py, 16, GetColor(0, 0, 255), TRUE);
}

enemy.h

//複数のファイルでインクルードするなどして複数のファイルで使うことになる変数にはexternをつける
extern int ex, ey, eflag;

void EnemyMove();

enemy.cpp

##nclude "DxLib.h"
#include "key.h"
#include "enemy.h"

int ex = 400, ey = 300, eflag = 0;

void EnemyMove()
{
    //移動する
    if (eflag == 0)
    {
        ex += 4;
    }
    else
    {
        ex -= 4;
    }

    //画面端で跳ね返る
    if (ex < 0)
    {
        eflag = 0;
    }
    if (ex > 800)
    {
        eflag = 1;
    }

    //画像を描画する関数
    //本来は画像描画用の関数を別に用意し、そちらに書くべきとされている
    DrawCircle(ex, ey, 16, GetColor(255, 0, 0), TRUE);
}

main.cpp

#include <DxLib.h>
#include "key.h"
#include "player.h"
#include "enemy.h"

int WINAPI WinMain( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow )
{
    ChangeWindowMode( TRUE );//非全画面にセット
    SetGraphMode( 800 , 600 , 32 );//画面サイズ指定
    SetOutApplicationLogValidFlag( FALSE ) ;//Log.txtを生成しないように設定
    if(DxLib_Init() == 1){return -1;}//初期化に失敗時にエラーを吐かせて終了

    //
    //ここで敵やプレイヤーのオブジェクトの実体を作る
    //

    while( ProcessMessage()==0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        keyupdate();

        ClearDrawScreen();//裏画面消す
        SetDrawScreen(DX_SCREEN_BACK);//描画先を裏画面に

        PlayerMove();
        EnemyMove();

        ScreenFlip();
    }

    DxLib_End();
    return 0;
}

C++ファイルを使用せず、ヘッダーファイルに宣言と定義の両方を書いた場合は、ヘッダーファイルから別のヘッダーファイルの変数や関数にアクセスする場合に、自身よりも後にインクルードされるファイルのものにはアクセスできないので気を付けたい。
(例 player.h → enemy.hの順にインクルードした場合、enemy.hからplayer.hの変数や関数にはアクセスできるが、player.hからenemy.hの方へアクセスすることができない。)