TOP

C言語ゲーム制作入門

プレイヤーを表示する

関数への参照渡し

※2014/3/24 リニューアルしました。 プログラムの内容は変わっていませんが、関数名、変数名などが変わっているので、 以前のものを見てくださった方は注意してください。

前ページ「迷路を作る」で作った迷路にプレイヤーを表示します。 プレイヤーの持つ情報は、現在いる「行」と「列」です。 これを前のページのMazeBlockと同様に構造体で表します。


//プレイヤー
typedef struct
{
  int row;        //プレイヤー位置(行)
  int column;     //プレイヤー位置(列)
}MazePlayer;

次にプレイヤーの位置をスタート地点に設定します。 プレイヤーを初期化する関数MazePlayerInitを次のように定義します。


//プレイヤー初期化
int MazePlayerInit(int *playerRow, int *playerColumn, MazeBlock maze[MAZE_ROW][MAZE_COLUMN])
{
  int i, j;

  for(i = 0; i < MAZE_ROW ; i++) //行
  {
    for(j = 0;  j < MAZE_COLUMN; j++) //列
    {
      if(maze[i][j].kind == START) //スタート地点ならばプレイヤーの位置に設定する
      {
        *playerRow    = i;
        *playerColumn = j;
        return 0; //関数終了(プレイヤーを設定できれば残りの処理は必要ない)
      }
    }
  }

  //スタート地点がなければ、プレイヤーを設定できずここまでくる
  printf("スタートがありません\n");
  return -1;
}

関数 MazePlayerInit
返り値
int 成功判定
引数
intへのポインタ型 playerRow プレイヤーのいる行
intへのポインタ型 playerColumn プレイヤーのいる列
MazeBlock型 maze[MAZW_ROW][MAZE_COLUMN] 迷路の情報
動作
引数playerRow、playerColumnにスタート地点の行と列を代入する。

返り値はintです。 スタート地点がなかった場合ゲームを始めることができないので、 スタート地点があった場合は0、なかった場合は-1を返して確認します。 この0と-1は勝手に決めたものなので、実際の値はなんでも構いません。

関数への参照渡し

引数のプレイヤーの行と列はポインタ型を渡していることに注意してください。 普通に関数に引数を渡すとき、その引数の値をコピーして扱います。 これを値渡しといいます。 引数は値をコピーしただけなので、関数内で引数の値を変えても、もとの変数の値を変えることはできません。 もとの変数の値を関数によって変える必要がある場合には、引数にポインタ型を指定します。 これを参照渡しといいます。

関数内ではfor文を使って迷路のブロックを一行一列から順に確認し、種類がSTARTになっているところを探します。 スタートが見つかると、プレイヤーの行と列に現在見ているブロックの行と列を代入します。 ポインタが指している値に代入するので関数内のplayerRow、playerColumnには*をつけています。 ポインタに*をつける場合は値そのもの、*をつけない場合は値のあるアドレスを表します。 今は値を変更するので*をつけているというわけです。 =の左右を見ると、*をつける場合は左右はどちらも値であるのに対し、*をつけない場合は左がアドレス、右が値なのでおかしいことがわかります。

代入が終わればreturnによって関数を途中で終了します。 returnが関数の途中で呼び出された場合は、残りの処理をせずに関数を終了します。 今回の迷路の場合、スタート地点は一行一列に設定したのでブロックを1個確認しただけで関数は終了し、残りの24個は確認しません(確認する必要もないので無駄です)。 25個確認しても値が返されていない場合、スタート地点がなかったことになるので、メッセージを出した後-1を返します。

最後に、前のページでつくった関数MazeDrawにプレイヤーを表示する機能を追加します。
プレイヤーは"P"で表すことにします。


//迷路表示
void MazeDraw(int playerRow, int playerColumn, MazeBlock maze[MAZE_ROW][MAZE_COLUMN])
{
  int i, j;

  for(i = 0; i < MAZE_ROW; i++) //行
  {
    for(j = 0; j < MAZE_COLUMN; j++ ) //列
    {
      if(i == playerRow && j == playerColumn) //プレイヤー位置
      {
        printf("P");
      }
      else if(maze[i][j].flag == FALSE) //ブロックが判明していない場合
      {
        printf("?");
      }
      else
      {
        switch(maze[i][j].kind)
        {
        case WALL:
          printf("口"); break; //壁
        case GOAL:
          printf("G"); break; //ゴール
        default:
          printf(" "); break; //その他(道、スタート)
        }
      }
    }
    printf("\n");
  }
}

関数 MazeDraw
返り値
void
引数
int型 playerRow プレイヤーのいる行
int型 playerColumn プレイヤーのいる列
MazeBlock型 maze[MAZE_ROW][MAZE_COLUMN] 迷路の情報
動作
引数playerRow、playerColumnおよび引数mazeの各kindの値によって、 プレイヤーと迷路のブロックをMAZE_ROW×MAZE_COLUMNの形に表示する。

前ページ「迷路を作る」と変更している部分は、 引数にプレイヤーの位置を追加したことと、 ブロックを見たとき最初にプレイヤーがいるブロックかどうかを確認し、 プレイヤーがいるブロックならプレイヤー表示するようにしたところです。

上のプレイヤーを初期化する関数MazePlayerInitと違い、引数に渡すplayerRow、playerColumnはただの値となります。 この関数ではプレイヤーの位置を変える必要はない、というようりむしろ変えてはいけないからです。 このような場合を考えて、関数では値の変更はできないようになっています。 関数内で変数の値を変更する必要があるかどうかを考えて、うまく参照渡しと値渡しを使い分けてください。

今回はプレイヤーの座標を表示する部分の下は必ずelse ifでつなげてください。 そうしないと、プレイヤーの座標を表示した後ブロックの種類も確認して表示することになり、プレイヤーがいる行だけ6文字表示することになってしまいます。

以上をまとめます。


#include <stdio.h>

#define MAZE_ROW    5      //迷路の行数
#define MAZE_COLUMN 5      //迷路の列数

//プレイヤー
typedef struct
{
  int row;        //プレイヤー位置(行)
  int column;     //プレイヤー位置(列)
}MazePlayer;


//迷路の一ブロック
enum MazeKind {PATH, WALL, START, GOAL};    //ブロックの種類(道、壁、スタート、ゴール)
enum MazeFlag {FALSE, TRUE};                //ブロックが判明しているかどうか

typedef struct
{
  enum MazeKind kind;            //種類(道、壁、スタート、ゴール)
  enum MazeFlag flag;            //ブロックが判明しているかどうか
} MazeBlock;


//プレイヤー初期化
int MazePlayerInit(int *playerRow, int *playerColumn, MazeBlock maze[MAZE_ROW][MAZE_COLUMN])
{
  int i, j;

  for(i = 0; i < MAZE_ROW ; i++) //行
  {
    for(j = 0;  j < MAZE_COLUMN; j++) //列
    {
      if(maze[i][j].kind == START) //スタート地点ならばプレイヤーの位置に設定する
      {
        *playerRow    = i;
        *playerColumn = j;
        return 0; //関数終了(プレイヤーを設定できれば残りの処理は必要ない)
      }
    }
  }

  //スタート地点がなければ、プレイヤーを設定できずここまでくる
  printf("スタートがありません\n");
  return -1;
}

//迷路表示
void MazeDraw(int playerRow, int playerColumn, MazeBlock maze[MAZE_ROW][MAZE_COLUMN])
{
  int i, j;

  for(i = 0; i < MAZE_ROW; i++) //行
  {
    for(j = 0; j < MAZE_COLUMN; j++ ) //列
    {
      if(i == playerRow && j == playerColumn) //プレイヤー位置
      {
        printf("P");
      }
      else if(maze[i][j].flag == FALSE) //ブロックが判明していない場合
      {
        printf("?");
      }
      else
      {
        switch(maze[i][j].kind)
        {
        case WALL:
          printf("口"); break; //壁
        case GOAL:
          printf("G"); break; //ゴール
        default:
          printf(" "); break; //その他(道、スタート)
        }
      }
    }
    printf("\n");
  }
}


int main(void)
{
  //プレイヤー
  MazePlayer player;

  //迷路
  MazeBlock maze[MAZE_ROW][MAZE_COLUMN] = 
    {
      { {START, TRUE } , {PATH , FALSE}, {PATH , FALSE}, {PATH , FALSE}, {PATH , FALSE} },
      { {WALL , FALSE} , {WALL , FALSE}, {PATH , FALSE}, {WALL , FALSE}, {WALL , FALSE} },
      { {WALL , FALSE} , {PATH , FALSE}, {PATH , FALSE}, {PATH , FALSE}, {PATH , FALSE} },
      { {PATH , FALSE} , {PATH , FALSE}, {WALL , FALSE}, {WALL , FALSE}, {WALL , FALSE} },
      { {WALL , FALSE} , {PATH , FALSE}, {PATH , FALSE}, {PATH , FALSE}, {GOAL , TRUE } },
    };

  //プレイヤー初期化
  if(MazePlayerInit(&player.row, &player.column, maze) == -1)
  {
    //関数MazePlayerInitが-1を返すとき初期化に失敗している
    //よって、この時点でプログラムを終了し、迷路の表示は行わない
    return 0;
  }

  //迷路表示
  MazeDraw(player.row, player.column, maze);

  return 0;
}

main関数内で、playerinitの引数はアドレスなので&をつけていることに注意してください。

main関数内ではif文の条件式でMazePlayerInitの返り値を確認しています。 スタート地点がない場合は0を返すので、条件式が成り立ち、returnを途中で呼び出して迷路を表示する前に終了します。 このようにしないと、スタート地点がなかった場合プレイヤーの位置をあらわすplayer.rowとplayer.columnが初期化されていないので、 予期しない挙動を示す可能性があります。

実行結果は以下のようになります。

P????
?????
?????
?????
????G

STARTをPATHに変えたときの結果なども確認してみてください。

次はプレイヤーを移動します。

inserted by FC2 system