Yamamoto's Laboratory

C言語編

キーボードの一つのキーでプログラムを実行?

キーボードのあるボタンを押したら,プログラムを実行させたい.可能でしょうか?

それは,不可能です.ただし,ユーザーにそのように思わせることは可能です.最も簡単な方法は,ほかのプログラムを予め実行させ,それから目的のプログラムをひとつのキー操作で実行させます.system()関数を使えば,簡単にできます.この関数を使うと,シェル(端末からの命令)に命令を与えることができます.次を参考にしてください.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  int p;

  printf("Which program do you want to run?\n");
  printf("a:program A\tb:program B\tq:quit\n");
  
  while(1){                      // 無限ループ 

    p = getchar();               // 文字読み込み 最初の1文字をpへ
    while(getchar()!='\n');      // バッファーを空に

    switch(p){
    case 'a':                    // 'a'が押されたときの処理
      system("プログラムの実行ファイル");
      break;

    case 'b':                    // 'b'が押されたときの処理
      system("プログラムの実行ファイル");
      break;

    case 'q':                    // 'q'が押されたときの処理
      printf("Bye bye\n");
      return 0;
      break;

    default:                     // 間違ったキーのときの処理
      printf("Your input key is wrong.\n");
    }

  }
  
  return 0;
}

たとえば,「hoge」という実行ファイルを実行させる場合は,system("./hoge")とします.

現在時刻を取得したい

現在時刻を取得したい.プログラムの書き方がわかりません.

インターネット上にいくつもプログラム例があります.適当にキーワードを入れて検索してください.参考に,私が作ったプログラム例を示します.関数time()で現在時刻を取得し,関数localtime()で日本時刻に直しています.

#include <stdio.h>
#include <time.h>

int main(void)
{
  time_t now;
  struct tm *jtime;

  time(&now);                                  // 日時の取得
  jtime=localtime(&now);                       // 日本時間に変換

  printf("present time: %s\n", ctime(&now));   // 日時を文字列に変換,表示

  printf("year:\t%d\n", jtime->tm_year+1900);  // 0->1900年 
  printf("month:\t%d\n", jtime->tm_mon+1);     // 0->1月    1->2月 
  printf("day:\t%d\n", jtime->tm_mday);
  printf("hour:\t%d\n", jtime->tm_hour);
  printf("minute:\t%d\n", jtime->tm_min);
  printf("second:\t%d\n", jtime->tm_sec);
  printf("\n");

  // 曜日と1月1日からの経過日もわかります
  printf("wday:\t%d\n", jtime->tm_wday);       // 0->sunday 1->monday
  printf("yday:\t%d\n", jtime->tm_yday);       // 0->Jan 1  1-> Jan 2

  return 0;
}

実行結果

present time: Sat Jan 19 21:20:37 2008

year:   2008
month:  1
day:    19
hour:   21
minute: 20
second: 37

wday:   6
yday:   18

OpenGL編

円の描き方が分かりません

OpenGLには円を描く関数がありません.多角形の辺を多くして円のように見せる—というような方法を使います.GL_POLYGONを使うことになります.

単純な方法

GL_POLYGONを使って,次のようにすれば円を描くことができます.

#include <stdio.h>
#include <GL/glut.h>
#include <math.h>

#define N 90                                // 円の分割数

void draw(void);                            // プロトタイプ宣言
void resize(int w, int h);

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(300,500);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA);           // 表示モードの指定
  glutCreateWindow("circle");               // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glClearColor(0.9, 1.0, 1.0, 1.0);         // 赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 図形を描く
//====================================================================
void draw(void)
{
  int i;

  glClear(GL_COLOR_BUFFER_BIT);
  glColor3d(0.0, 0.7, 0.0);            // 線の色指定(RGB) 緑

  // ---- 円を描く ---------

  glBegin(GL_POLYGON);
  for(i=0; i<N; i++){
    glVertex2d(cos(2.0*M_PI/N*i),sin(2.0*M_PI/N*i));   // 頂点の指定
  }
  glEnd();                                              // 終了

  glFlush();                           // 描画
}

//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{

  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(-w/200.0, w/200.0, -h/200.0, h/200.0); // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定
}

少しばかり高速化

三角関数の計算には時間がかかります.アニメーションなどに使う場合は,同じような計算は避けるべきです.次のように三角関数の計算結果は配列に格納する方が良いでしょう.このようにすれば,半径や原点が変化しても,短時間で計算できます.

#include <stdio.h>
#include <GL/glut.h>
#include <math.h>

#define N 90                                // 円の分割数

void draw(void);                            // プロトタイプ宣言
void resize(int w, int h);
void set_circle(void);

double x[N], y[N];                          // グローバル変数
//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(300,300);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA);           // 表示モードの指定
  glutCreateWindow("circle");               // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glClearColor(0.9, 1.0, 1.0, 1.0);         // 赤緑青と透明度
  set_circle();

  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 図形を描く
//====================================================================
void draw(void)
{
  int i;

  glClear(GL_COLOR_BUFFER_BIT);

  // ---- 円 中心座標:(0,0) 半径:0.5, ---------
  glColor3d(0.0, 0.7, 0.0);            // 線の色指定(RGB) 緑
  glBegin(GL_POLYGON);
  for(i=0; i<N; i++){
    glVertex2d(0.5*x[i], 0.5*y[i]);
  }
  glEnd();

  // ---- 円 中心座標:(0.7,-0.5) 半径:0.3, ---------
  glColor3d(0.0, 0.0, 0.7);            // 線の色指定(RGB) 青
  glBegin(GL_POLYGON);
  for(i=0; i<N; i++){
    glVertex2d(0.3*x[i]+0.7, 0.3*y[i]-0.5);
  }
  glEnd();
  
  glFlush();                           // 描画
}

//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{

  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(-w/200.0, w/200.0, -h/200.0, h/200.0); // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定
}

//====================================================================
// 中心座標(0,0) 半径1 の円の座標を配列に
//====================================================================
void set_circle(void)
{
  int i;

  for(i=0; i<N; i++){
    x[i]=cos(2.0*M_PI/N*i);
    y[i]=sin(2.0*M_PI/N*i);
  }

}

キーボードイベントに矢印キーを使いたい

キーボードイベントで矢印キーを使う場合には,glutSpecialFunc()を使います.以下の例を参考にしてください.

#include <stdio.h>
#include <GL/glut.h>
#include <math.h>

#define N 90                                // 円の分割数

// --------- プロトタイプ宣言 -------------
void skey_in(int key, int x, int y);
void draw(void);
void resize(int w, int h);
void set_circle(void);

// --------- グローバル変数 -------------
double x[N], y[N];                          // 半径:1 中心:(0,0)の円
double xc=0.0, yc=0.0;                      // 円の中心

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(400,400);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA);           // 表示モードの指定
  glutCreateWindow("circle");               // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutSpecialFunc(skey_in);                 // 特殊キーイベント
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glClearColor(0.9, 1.0, 1.0, 1.0);         // 赤緑青と透明度
  set_circle();

  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 矢印キーが押された場合のイベントの処理
//====================================================================
void skey_in(int key, int x, int y)         // xとyはマウスの座標
{
  if(key==GLUT_KEY_LEFT)  xc-=0.03;            // 左矢印が押されたとき
  if(key==GLUT_KEY_RIGHT) xc+=0.03;            // 右矢印が押されたとき
  if(key==GLUT_KEY_DOWN)  yc-=0.03;            // 下矢印が押されたとき
  if(key==GLUT_KEY_UP)    yc+=0.03;            // 上矢印が押されたとき

  glutPostRedisplay();                         // 再描画
}

//====================================================================
// 図形を描く
//====================================================================
void draw(void)
{
  int i;

  glClear(GL_COLOR_BUFFER_BIT);

  glColor3d(0.0, 0.0, 0.7);            // 線の色指定(RGB) 青
  glBegin(GL_POLYGON);
  for(i=0; i<N; i++){
    glVertex2d(x[i]+xc, y[i]+yc);
  }
  glEnd();
  
  glFlush();                           // 描画
}


//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(-w/200.0, w/200.0, -h/200.0, h/200.0); // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定
}

//====================================================================
// 中心座標(0,0) 半径1 の円の座標を配列に
//====================================================================
void set_circle(void)
{
  int i;

  for(i=0; i<N; i++){
    x[i]=cos(2.0*M_PI/N*i);
    y[i]=sin(2.0*M_PI/N*i);
  }

}

そのほかの特殊キーは次のようになっています.

特殊キーの値.これらは glut.h で定義されている.
定数 特殊キー
GLUT_KEY_LEFT 左矢印キー
GLUT_KEY_UP 上矢印キー
GLUT_KEY_RIGHT 右矢印キー
GLUT_KEY_DOWN 下矢印キー
GLUT_KEY_PAGE_UP Page Up キー
GLUT_KEY_PAGE_DOWN Page Down キー
GLUT_KEY_HOME Home キー
GLUT_KEY_END End キー
GLUT_KEY_INSERT Inset キー
GLUT_KEY_F1 ファンクションキー F1
GLUT_KEY_F2 ファンクションキー F2
   以下,F12まで同じ
GLUT_KEY_F12 ファンクションキー F12

ビットマップファイルの絵を貼り付けたい

ビットマップファイルの図形をウィンドウに貼り付けたい?方法を教えてください.

ビットマップファイルのデータを読み込み,それをglDrawPixels()を使って,表示させることができます.次のようにプログラムを記述します.

#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

typedef struct{          // ビットマップファイルを読み込むための構造体
  unsigned short file_type;
  unsigned long  file_size;
  unsigned short file_reserved1;
  unsigned short file_reserved2;
  unsigned long  file_offset;
  unsigned long  info_size;
  long           info_width;
  long           info_height;
  unsigned short info_planes;
  unsigned short info_bit_count;
  unsigned long  info_compression;
  unsigned long  info_size_image;
  long           info_x_pix_per_meter;
  long           info_y_pix_per_meter;
  unsigned long  info_n_palett;
  unsigned long  info_index_palett;
  unsigned char *pixell;
}BitmapFileData;

BitmapFileData pict;

void draw(void);                            // プロトタイプ宣言
void resize(int w, int h);
int read_bitmap(char *file_name);

/*=======================================================================*/
/*  main function                                                        */
/*=======================================================================*/
int main(int argc, char *argv[])
{

  read_bitmap("ビットマップファイル名");
  
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(500,600);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA);           // 表示モードの指定
  glutCreateWindow("show bitmap");          // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glClearColor(1.0, 1.0, 1.0, 1.0);         // 赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

/*=======================================================================*/
/*  set bitmap file datas to memory from disk                            */
/*=======================================================================*/
int read_bitmap(char *file_name)
{
  unsigned int lb;
  unsigned int i,j;
  unsigned int position;
  FILE *fp;

  fp=fopen(file_name,"r");

  /*----- read file header  --------------*/
  fread(&(pict.file_type), 2, 1, fp);
  fread(&(pict.file_size), 4, 1, fp);
  fread(&(pict.file_reserved1), 2, 1, fp);
  fread(&(pict.file_reserved2), 2, 1, fp);
  fread(&(pict.file_offset), 4, 1, fp);

  /*----- read info header  --------------*/
  fread(&(pict.info_size), 4, 1, fp);
  fread(&(pict.info_width), 4, 1, fp);
  fread(&(pict.info_height), 4, 1, fp);
  fread(&(pict.info_planes), 2, 1, fp);
  fread(&(pict.info_bit_count), 2, 1, fp);
  fread(&(pict.info_compression), 4, 1, fp);
  fread(&(pict.info_size_image), 4, 1, fp);
  fread(&(pict.info_x_pix_per_meter), 4, 1, fp);
  fread(&(pict.info_y_pix_per_meter), 4, 1, fp);
  fread(&(pict.info_n_palett), 4, 1, fp);
  fread(&(pict.info_index_palett), 4, 1, fp);

  /*----- read the bitmap pixel datas  --------------*/

  pict.pixell=calloc(4*pict.info_width*pict.info_height, sizeof(unsigned char));

  /* lb: byte/holizontal line  */
  lb = (pict.info_width * pict.info_bit_count) / 8;

  if ((lb % 4) != 0) {
    lb = ((lb / 4) + 1) * 4;                 /* adjust 4byte */
  }


  for(i = 0; i < pict.info_height; i++){
    position = pict.file_offset + lb * i;
    fseek(fp, position, SEEK_SET);
    for(j = 0; j < pict.info_width; j++){
      *(pict.pixell+i*pict.info_width*4+j*4+3)=0xff;    // 透明度
      fread(pict.pixell+i*pict.info_width*4+j*4+2, 1, 1, fp);
      fread(pict.pixell+i*pict.info_width*4+j*4+1, 1, 1, fp);
      fread(pict.pixell+i*pict.info_width*4+j*4+0, 1, 1, fp);
    }
  }

  fclose(fp);

  return 0;
}

//====================================================================
// 図形を描く
//====================================================================
void draw(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  glRasterPos2d(100,100);                      // 画像の左下座標
  glDrawPixels(pict.info_width, pict.info_height, 
	       GL_RGBA, GL_UNSIGNED_BYTE, pict.pixell);  // 描画
 
  glFlush();                           
}

//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(0, w, 0, h);       // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定
}

皆さんが,このプログラムを使うときには,ビットマップファイルのデータを格納する構造体(BitmapFileData)と読み込みの関数(read_bitmap())をそのままコピーして使えばよいでしょう.

ネットを見るといろいろな方法があるようです.例えば,床井さんの第一回画像の読み込みのページを見てください.

点を表示させて移動させたい

glBegin(GL_POINTS)を使います.点の座標はglVertex2d()で,大きさはglPointSize()で決めることができます.

点が移動して,ウィンドウの端に達したら反射するプログラムを例に示します.プログラムの実行状態のスナップショットを下図に示します.プログラムのリストをよく見て,点の表示の仕方とそれの動かす方法を理解してください.

点の情報の格納に構造体を使っています.構造体を使うと,情報を上手に管理できます.一つの点の情報を point とという構造体で管理しています.このプログラムは,100個の動き回る点を描がいており,それらのデータは point 型の構造体の配列 pt[100]に格納しています.諸君は,構造体およびそれを配列にして使う方法に慣れよ!

点が動き回る
#include <stdio.h>
#include <stdlib.h>                         // 乱数を使うために
#include <GL/glut.h>
#include <math.h>

#define N 100                               // 点の数

//---- プロトタイプ宣言 -----
void set_points(void);                      // 点の初期値
void draw(void);                            // 図を描く
void resize(int w, int h);                  // サイズの調整
void anime(void);                           // アニメーション

//---- 構造体の宣言 -------
typedef struct{
  double size;                              // 点のサイズ
  double red, green, blue;                  // 点の色
  double x, y;                               // 点の座標
  double vx, vy;                             // 点の速度
}point;

//---- グローバル変数 -------
point pt[N];
int win_w;
int win_h;

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{
  set_points();
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(200,200);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);           // 表示モードの指定
  glutCreateWindow("move points");          // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glutIdleFunc(anime);                      // 暇なときに実行(アニメーション)
  glClearColor(0.0, 0.0, 0.0, 1.0);         //赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 点の初期値を決める
//====================================================================
void set_points(void)
{
  int i;
 
  for(i=0; i<N; i++){
    pt[i].size=(double)rand()/RAND_MAX*3;

    pt[i].red=(double)rand()/RAND_MAX;
    pt[i].green=(double)rand()/RAND_MAX;
    pt[i].blue=(double)rand()/RAND_MAX;

    pt[i].x=(double)rand()/RAND_MAX*200;
    pt[i].y=(double)rand()/RAND_MAX*200;

    pt[i].vx=(double)rand()/RAND_MAX*0.1;
    pt[i].vy=(double)rand()/RAND_MAX*0.1;

  }
}

//====================================================================
// 点を描く
//====================================================================
void draw(void)
{
  int i;

  glClear(GL_COLOR_BUFFER_BIT);

  // ---- 点を打つ ---------
  for(i=0; i<N; i++){
    glColor3d(pt[i].red, pt[i].green, pt[i].blue);    // 点の色指定(RGB)
    glPointSize(pt[i].size);                          // サイズの指定
    glBegin(GL_POINTS);
    glVertex2d(pt[i].x, pt[i].y);
    glEnd();
  }

  glutSwapBuffers();                                // 描画
}


//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(0, w, 0, h);       // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定

  win_w=w;
  win_h=h;
}

//====================================================================
// PCが暇なときに実行する.これが実行されるとアニメーションが描かれる
//====================================================================
void anime(void)
{
  int i;
 
  for(i=0; i<N; i++){
    pt[i].x+=pt[i].vx;
    pt[i].y+=pt[i].vy;

    if(pt[i].x < 0 || win_w < pt[i].x) pt[i].vx *= -1;   // windowの端で
    if(pt[i].y < 0 || win_h < pt[i].y) pt[i].vy *= -1;   // 反射する
  }

  glutPostRedisplay();
}

文字はどのようにして表示させるのですか?

OpenGLで作成したウインドウの中に文字を表示させる方法がわかりません.

文字を表させるには,glutBitmapCharacter(フォント指定,文字)を使います.この関数は次のようになっています.(参考)

void glutBitmapCharacter(void *font, int char)
fontは以下の表のようなものが選択できます.第二引数はで文字(ASCIIコード)です.

以下のフォントを使うことができます.

font フォント名 サイズ
GLUT_BITMAP_8_BY_13 固定幅フォント 8×13ピクセルの長方形にフィット
GLUT_BITMAP_9_BY_15 固定幅フォント 9×15ピクセルの長方形にフィット
GLUT_BITMAP_TIMES_ROMAN_10 Times Roman 10ポイント
GLUT_BITMAP_TIMES_ROMAN_24 Times Roman 24ポイント
GLUT_BITMAP_HELVETICA_10 Helvetica 10ポイント
GLUT_BITMAP_HELVETICA_12 Helvetica 12ポイント
GLUT_BITMAP_HELVETICA_18 Helvetica 18ポイント

文字を出力する場合,色と座標も指定します.色の指定には,関数glColor3f(,,)を使います.座標の指定には,関数glRasterPos2d(x座標,y座標)を使います.ここで指定する座標は文字の左下です.連続的に文字を出力する場合,ひと文字毎に座標を指定する必要はありません.自動的に次に文字を出力する座標に移動します.これらの具体的な使い方は,以下の例を見てください.色や座標は,このほかの関数でも指定できます.

#include <stdio.h>
#include <GL/glut.h>
#include <string.h>

//---- プロトタイプ宣言 -----
void draw(void);                            // 図を描く
void resize(int w, int h);                  // サイズの調整

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{

  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(400,200);              // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);           // 表示モードの指定
  glutCreateWindow("strings");              // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glClearColor(1.0, 1.0, 1.0, 1.0);         //赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 文字を書く
//====================================================================
void draw(void)
{
  int i=0, scoreA;
  char *text_pt;
  char text_ar[256], text_sp[256];


  text_pt="using pointer";
  strcpy(text_ar, "using array");
  scoreA = 123;
  sprintf(text_sp, "your score %d", scoreA);

  glClear(GL_COLOR_BUFFER_BIT);

  // ---- 文字を書く ---------

  glColor3f(1, 0, 0);                         // 色設定
  glRasterPos2d(10,150);                      // 文字の場所
  for(i=0; text_pt[i]!='\0'; i++){
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text_pt[i]);
  }

  glColor3f(0, 1, 0);
  glRasterPos2d(10,130);
  for(i=0; text_ar[i]!='\0'; i++){
    glutBitmapCharacter(GLUT_BITMAP_9_BY_15, text_ar[i]);
  }

  glColor3f(0, 0, 1);
  glRasterPos2d(10,110);
  for(i=0; text_sp[i]!='\0'; i++){
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, text_sp[i]);
  }
  
  glutSwapBuffers();                                // 描画
}


//====================================================================
// リサイズ
//====================================================================
void resize(int w, int h)
{

  glLoadIdentity();             // 変換行列を単位行列に
  gluOrtho2D(0, w, 0, h);       // world座標系の範囲
  glViewport(0, 0, w, h);       // ウィンドウ座標系を指定

}

文字列を出力する場合,文字列をメモリーに格納して,ひと文字ずつ取り出す必要があります.配列を使ったり,ポインターを使う方法があります.ここの例では,3通りの方法でメモリーに格納しています.

ゲームのプログラムを再スタートさせたい

"Game over"の後,再スタートさせたい.どのようにするのですか?

メッセージを表示させた後,適当なキーを押すとゲームを再スタートさせると良いでしょう.たとえば,授業で使ったインベーダーのプログラムを再スタートさせるプログラムの例を示します.プログラム中の赤文字部分を追加しました.

#include <stdio.h>
#include <GL/glut.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define H_WIN 400                      // ウィンドウの幅
#define W_WIN 300                      // ウィンドウの高さ

#define W2_HODAI 10                    // 砲台の横幅の半分
#define H_HODAI 15                     // 砲台の上面のy座標
#define L_HODAI 5                      // 砲台の下面のy座標
#define L_E_BEAM 20                    // 防衛軍のビームの長さ
#define V_E_BEAM 1.5                   // 防衛軍のビームの速度
#define N_E_BEAM 1                     // 防衛軍のビームの画面上の最大数

#define L_I_BEAM 10                    // インベーダー軍のビームの長さ
#define V_I_BEAM 0.8                   // インベーダー軍のビームの速度
#define P_I_BEAM 500                   // インベーダー軍のビームの初期発射確率

#define N_I_BEAM 20                    // インベーダー軍のビームの画面上の最大数
#define NXIV 9                         // インベーダー軍の列の数
#define NYIV 4                         // インベーダー軍の行の数
#define V_INVADER 0.1                  // インベーダー軍の速度

#define NOT_DECIDE 0
#define INVADER 1
#define HUMAN 2

//---- プロトタイプ宣言 -----
void initialize(void);                      // 初期化

void draw(void);                            // 図を描く
void draw_result(void);                     // 結果表示
void draw_hodai(void);                      // 防衛軍の砲台の描画
void draw_e_beam(void);                     // 防衛軍のビームの描画
void draw_i_beam(void);                     // インベーダー軍のビームの描画
void draw_invader(void);                    // インベーダー軍の描画

void change_state(void);                    // 状態変化に関する処理
void state_e_beam(void);                    // 防衛軍のビームの状態変化
void state_invader(void);                   // インベーダー軍の状態変化
void state_i_beam(void);                    // インベーダー軍のビーム状態変化

void mouse_xy(int x, int y);
void shoot(unsigned char key, int x, int y); // 防衛軍ビーム発射

void resize(int w, int h);                  // サイズの調整
void set_color(void);                       // 塗りつぶし色の設定

//---- グローバル変数 -------
double xc = 100.0;                          // マウスのx座標

typedef struct{
  unsigned char status;                     // 0:dead 1:alive
  double x, y;                              // 中心座標
}invader;

invader invd[NXIV][NYIV];                   // インベーダー
int alive_inv=NXIV*NYIV;                    // 生きているインベーダーの数
double inv_vx=V_INVADER;                    // インベーダーの横方向の速度


typedef struct{
  char status;                              // 0:格納庫 1:砲台の上 2:移動中
  double x;                                 // ビームのx座標
  double y0, y1;                            // ビームのy座標 y0:先頭 y1:最後尾
  double vy;                                // ビームの速度
}beam;

beam e_beam[N_E_BEAM];                      // 地球防衛軍のビーム
beam *p_e_beam1;                            // 地球防衛軍の次に発射可能なビーム
beam i_beam[N_I_BEAM];                      // インベーダー軍のビーム

int winner = NOT_DECIDE;
char *win="You won a game.";
char *lost="You lost a game.";
char *push_key="push a key";
char *quit_new="n:new game      q:quit";

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{

  initialize();
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(W_WIN,H_WIN);          // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);   // 表示モードの指定
  glutCreateWindow("space invader modoki");       // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glutIdleFunc(change_state);               // 暇なときに実行(状態の変化)
  glutPassiveMotionFunc(mouse_xy);          // マウスイベント(砲台の移動)
  glutKeyboardFunc(shoot);                  // キーボードイベント(ビームを発射)
  set_color();                              // 塗りつぶす色指定
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 初期化
//====================================================================
void initialize(void)
{
  int i, j;

  srand((unsigned int)time(NULL));        // 乱数を発生させるため

  for(i=0; i<N_E_BEAM; i++){
    e_beam[i].status=0;
    e_beam[i].y0=H_HODAI+L_E_BEAM;
    e_beam[i].y1=H_HODAI;
    e_beam[i].vy=0.0;
  }

  e_beam[0].status=1;                       // 砲台にのせる
  p_e_beam1=&e_beam[0];

  for(i=0; i<N_I_BEAM; i++){
    i_beam[i].status = 0;
    i_beam[i].y0 = 0;
    i_beam[i].y1 = 0;
    i_beam[i].vy = V_I_BEAM;
  }

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      invd[i][j].status=1;
      invd[i][j].x = 20*(i+1);            // x,yとも20ピクセル間隔
      invd[i][j].y = H_WIN - NYIV*20+10+20*j;
    }
  }
}


//====================================================================
// 図を描く
//====================================================================
void draw(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  if(winner != NOT_DECIDE) draw_result();

  draw_hodai();         // 砲台を描く関数呼び出し
  draw_e_beam();        // 地球防衛軍のビームを描く関数の呼び出し
  draw_i_beam();        // インベーダー軍のビームを描く関数の呼び出し
  draw_invader();       // インベーダーを描く関数の呼び出し
  
  glutSwapBuffers();    // 描画
}


//====================================================================
// 勝者の表示
//====================================================================
void draw_result(void)
{
    int i=0;

    glColor3d(0.0, 1.0, 0.0);

    if(winner==HUMAN){
      while(win[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,win[i]);
	i++;
      }
    }else if(winner==INVADER){
      while(lost[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,lost[i]);
	i++;
      }
    }

    // ゲームリスタート or 終了 に関するメッセージ表示
    glColor3d(1.0, 0.0, 0.0);
    glRasterPos2i(30,160);
    for(i=0; push_key[i]!='\0'; i++){
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,push_key[i]);
    }
    glColor3d(0.7, 0.0, 0.0);
    glRasterPos2i(40,140);
    for(i=0; quit_new[i]!='\0'; i++){
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,quit_new[i]);
    }
    
}


//====================================================================
// 地球防衛軍の砲台の描画
//====================================================================
void draw_hodai(void)
{
  glColor3d(0.5, 1.0, 0.5);            // 線の色指定(RGB)
  glBegin(GL_POLYGON);
  glVertex2d(xc-W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, H_HODAI);
  glVertex2d(xc-W2_HODAI, H_HODAI);
  glEnd();
}


//====================================================================
// 地球防衛軍のビーム砲の描画
//====================================================================
void draw_e_beam(void)
{
  int i;

  for(i=0;i<N_E_BEAM;i++){
    if(e_beam[i].status != 0){
      glColor3d(1.0, 0.0, 0.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(e_beam[i].x, e_beam[i].y0);
      glVertex2d(e_beam[i].x, e_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍のビームの描画
//====================================================================
void draw_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status == 2){
      glColor3d(0.0, 0.0, 1.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(i_beam[i].x, i_beam[i].y0);
      glVertex2d(i_beam[i].x, i_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍の描画
//====================================================================
void draw_invader(void)
{
  int i, j;     // インベーダーのi列j行

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){    // 生きているインベーダーのみを描く

	//------ 胴体 ----------------------
	glColor3d(1.0, 1.0, 1.0);
	glBegin(GL_POLYGON);
	glVertex2d(invd[i][j].x-8, invd[i][j].y);
	glVertex2d(invd[i][j].x-3, invd[i][j].y-4);
	glVertex2d(invd[i][j].x+3, invd[i][j].y-4);
	glVertex2d(invd[i][j].x+8, invd[i][j].y);
	glVertex2d(invd[i][j].x+3, invd[i][j].y+4);
	glVertex2d(invd[i][j].x-3, invd[i][j].y+4);
	glEnd();

	//------- 手 足 触覚 -----------------------
	glBegin(GL_LINES);
	glVertex2d(invd[i][j].x-7, invd[i][j].y);    // 左手
	glVertex2d(invd[i][j].x-7, invd[i][j].y+6);
	glVertex2d(invd[i][j].x+7, invd[i][j].y);    // 右手
	glVertex2d(invd[i][j].x+7, invd[i][j].y+6);
	glVertex2d(invd[i][j].x-4, invd[i][j].y-4);  // 左足
	glVertex2d(invd[i][j].x-6, invd[i][j].y-8);
	glVertex2d(invd[i][j].x+4, invd[i][j].y-4);  // 右足
	glVertex2d(invd[i][j].x+6, invd[i][j].y-8);
	glVertex2d(invd[i][j].x-2, invd[i][j].y+4);  // 左触覚
	glVertex2d(invd[i][j].x-5, invd[i][j].y+6);
	glVertex2d(invd[i][j].x+2, invd[i][j].y+4);  // 右触覚
	glVertex2d(invd[i][j].x+5, invd[i][j].y+6);
	glEnd();

	//------- 目玉 ----------------
	glColor3d(0.0, 0.0, 0.0);
	glBegin(GL_POLYGON);             // 左目
	glVertex2d(invd[i][j].x-3, invd[i][j].y);
	glVertex2d(invd[i][j].x-1, invd[i][j].y);
	glVertex2d(invd[i][j].x-1, invd[i][j].y+2);
	glVertex2d(invd[i][j].x-3, invd[i][j].y+2);
	glEnd();
	glBegin(GL_POLYGON);             // 右目
	glVertex2d(invd[i][j].x+3, invd[i][j].y);
	glVertex2d(invd[i][j].x+1, invd[i][j].y);
	glVertex2d(invd[i][j].x+1, invd[i][j].y+2);
	glVertex2d(invd[i][j].x+3, invd[i][j].y+2);
	glEnd();
      }
    }
  }
}


//====================================================================
// リサイズ
//     この関数は window のサイズが変化したら呼び出される
//     引数
//            w:ウィンドウの幅
//            h:ウィンドウの高さ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();                   // 変換行列を単位行列に
  gluOrtho2D(0, W_WIN, 0, H_WIN);     // world座標系の範囲
  glViewport(0, 0, w, h);             // ウィンドウ座標系を指定
}


//====================================================================
// PCが暇なときに実行する.これが実行されると状態が変化する
//====================================================================
void change_state(void)
{

  if(winner == NOT_DECIDE){
    state_e_beam();     // 地球防衛軍のビームの処理
    state_invader();    // インベーダー軍の処理
    state_i_beam();     // インベーダー軍のビームの処理
  }

  glutPostRedisplay();
}


//====================================================================
// 地球防衛軍のビームの状態の処理
//====================================================================
void state_e_beam(void)
{
  int i,l,m;
  int st0=0;
  int rdy=0;
  int nshoot=0;                 // 発射済みの地球防衛軍の玉の数
  double min_y=H_WIN+L_E_BEAM;   // 最もしたのミサイルの先のy座標
  double ydis;                  // 最も下のミサイルと発射台の距離

  for(i=0; i<N_E_BEAM; i++){
    switch(e_beam[i].status){

    //--------  格納庫にあるビームの処理 ------------------------
    case 0:
      st0=i;                    // 次に発射可能なビームを設定
      break;

    //--------  砲台にあるビームの処理 ------------------------
    case 1:
      e_beam[i].x = xc;         // x方向に移動
      rdy=1;                    // 砲台にビームがあることを示すフラグをON
      break;

    //--------  すでに発射されたビームの処理 ------------------------
    case 2:
      nshoot++;                         // 発射されているビームをカウント
      e_beam[i].y0 += e_beam[i].vy;     // ビームの移動
      e_beam[i].y1 += e_beam[i].vy;

      // ------ インベーダーにビームが衝突したことを確認して処理 ------
      for(l=0; l<NXIV; l++){    
	for(m=0; m<NYIV; m++){
	  if(invd[l][m].status==1){
	    if((invd[l][m].x-8 < e_beam[i].x) &&
	       (e_beam[i].x < invd[l][m].x+8) &&
	       (invd[l][m].y-4 < e_beam[i].y0) &&
	       (e_beam[i].y1 < invd[l][m].y+4)){
	      invd[l][m].status=0;            // インベーダーの死亡
	      alive_inv--;                    // 生きているインベーダーの数を-1
	      if(alive_inv==0)winner=HUMAN;
	      e_beam[i].status=0;             // ビームは格納庫へ
	      e_beam[i].y0=H_HODAI+L_E_BEAM;  // ビームの初期化
	      e_beam[i].y1=H_HODAI;
	    }
	  }
	}      
      }


      // ---- 画面から地球防衛軍のビームがはみ出た場合の処理 --------
      if(H_WIN+L_E_BEAM < e_beam[i].y0){
	e_beam[i].status = 0;
	e_beam[i].y0 = H_HODAI+L_E_BEAM;
	e_beam[i].y1 = H_HODAI;
	e_beam[i].vy = 0.0;
      }
      if(e_beam[i].y0 < min_y) min_y=e_beam[i].y0;
      break;
    default:
      printf("e_beam status error!!\n");
      exit(1);
    }
  }


  // --- 地球防衛軍の新たな発射可能なビームの処理 -----
  ydis = min_y-H_HODAI;
  if( (2.5*L_E_BEAM < ydis) && (rdy==0) && (nshoot<N_E_BEAM) ){
    e_beam[st0].status=1;
    p_e_beam1=(beam *)&e_beam[st0];     // 発射可能なビームをポインターで表現
  }
}


//====================================================================
// インベーダー軍の状態の処理
//====================================================================
void state_invader(void)
{
  int i, j, k;
  double ivmin_x=W_WIN, ivmax_x=0;
  double ivmin_y=H_WIN, ivmax_y=0;
  int can_attack;

  for(i=0; i<NXIV; i++){
    can_attack=1;
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){   // インベーダーの生死のチェック
	invd[i][j].x += inv_vx;   // インベーダーの横方向移動
	// ---- インベーダー軍のビーム発射の処理 ------
	if(can_attack == 1 && rand()%P_I_BEAM == 0){  // 発射条件
	  for(k=0; k<N_I_BEAM; k++){
	    if(i_beam[k].status !=2){      // 発射可能なビームを探す
	      i_beam[k].status =2;         // ビームの発射
	      i_beam[k].x = invd[i][j].x;
	      i_beam[k].y0 = invd[i][j].y;
	      i_beam[k].y1 = invd[i][j].y-L_I_BEAM;
	      break;
	    }
	  }
	}
	// --- インベーダー軍の左右上下の端の座標 -------
	if(invd[i][j].x < ivmin_x) ivmin_x=invd[i][j].x;   // 左端 
	if(invd[i][j].x > ivmax_x) ivmax_x=invd[i][j].x;   // 右端
	if(invd[i][j].y < ivmin_y) ivmin_y=invd[i][j].y;   // 下の端
	if(invd[i][j].y > ivmax_y) ivmax_y=invd[i][j].y;   // 上の端
	can_attack=0;
      }
    }
  }


  if(ivmin_x < 10) inv_vx = V_INVADER;           // 左端に達したとき
  if(ivmax_x > W_WIN-10) inv_vx = -V_INVADER;    // 右端に達したとき
  
  if((ivmin_x < 10) || (ivmax_x > W_WIN-10)){    // 左右の端に達しとき
    for(i=0; i<NXIV; i++){
      for(j=0; j<NYIV; j++){
	invd[i][j].y -= 10;                       // 下に降りる
      }
    }
  }
}


//====================================================================
// インベーダー軍のビームの状態の処理
//====================================================================
void state_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status ==2){
      i_beam[i].y0 -= i_beam[i].vy;
      i_beam[i].y1 -= i_beam[i].vy;
   
      if(i_beam[i].y1 < 0) i_beam[i].status=0;

      if((xc-W2_HODAI < i_beam[i].x) &&
	 (i_beam[i].x < xc+W2_HODAI) &&
	 (L_HODAI < i_beam[i].y0) &&
	 (i_beam[i].y1 < H_HODAI)){
	winner=INVADER;            // 地球防衛軍の負け
      }
    }
  }
}


//====================================================================
// マウスイベントの処理
//====================================================================
void mouse_xy(int x, int y)
{
  xc=x;                // マウスのx座標をグローバル変数の xc へ代入
}


//====================================================================
// キーボードイベントの処理
// スペースキーが押されたら地球防衛軍のビームを発射
//====================================================================
void shoot(unsigned char key, int x, int y)
{
  //--- スペースキーが押されて,発射可能なビームがあるとき ----
  if(key==' ' && p_e_beam1 != NULL){
    p_e_beam1->status = 2;            // ビームを発射の状態にする
    p_e_beam1->vy = V_E_BEAM;         // ビームの速度を設定
    p_e_beam1 = NULL;                 // 発射可能なビームが無い
  }

  //----- game 終了とリスタートの選択 -----
  if(winner != NOT_DECIDE){
    if(key=='q'){
      exit(0);
    }else if(key=='n'){
      initialize();                   // 初期化
      winner = NOT_DECIDE;            // 勝者は未定に
      alive_inv=NXIV*NYIV;            // 生きているインベーダーの数
    }
  }

}


//====================================================================
// 色の指定
//====================================================================
void set_color(void)
{
  glClearColor(0.0, 0.0, 0.0, 1.0);        //赤緑青と透明度
}

このテクニックは,ゲームに勝って,1面をクリアーした場合も使えます.難易度を上げて,次のゲームをスタートさせると,よりおもしろくなるでしょう.

インベーダーのキャラクターを変化させたい

インベーダーのキャラクターが,変化しないので,つまらない.ゲームの進行とともに変化させる方法を教えてください.

ゲームの進行とともに,インベーダーのキャラクターの描き方を変えれば,それは可能です.何かの情報により,形の異なるインベーダーを書けばよい.その書き方の例を示します.

キャラクターをOpenGLの命令を使って記述するのは面倒なので,予めファイルにしておくと良いでしょう.ここでの例では,予めキャラクターをビットマップファイルにしておきます.次のようなキャラクターを用意しました.これらのキャラクターは,お絵かきソフトウェアーで作成してビットマップファイルに保存します.私はGIMPを使いました.

キャラクターが用意できたならば,ビットマップファイルの読み込みを行い,ゲームの状況に応じて表示させれば良いでしょう.ここで示す例では,インベーダーが10ピクセル移動するとキャラクターが変化するようにしています.プログラムの仕方は,様々な方法があります.私は次のようにしました.

  • 各インベーダーの情報を管理している構造体に,キャラクターの情報を追加.
  • ビットマップファイルからキャラクターを読み込む関数を追加.
  • インベーダーが10ピクセル移動したらキャラクターを変化させるルーチンを追加.
  • インベーダーを描くときの位置の計算を追加.キャラクターを描くときには左下の座標を指定する.構造体中のインベーダーの位置情報は,その中心座標となっているので注意が必要.

次のように,インベーダーのキャラクターが変化します.

プログラム例を以下に示します.このプログラムを実行させるためには,キャラクターのビットマップファイル(1, 2, 3, 4, 5, 6)が必要です.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <GL/glut.h>

#define H_WIN 400                      // ウィンドウの幅
#define W_WIN 300                      // ウィンドウの高さ

#define W2_HODAI 10                    // 砲台の横幅の半分
#define H_HODAI 15                     // 砲台の上面のy座標
#define L_HODAI 5                      // 砲台の下面のy座標
#define L_E_BEAM 20                    // 防衛軍のビームの長さ
#define V_E_BEAM 1.5                   // 防衛軍のビームの速度
#define N_E_BEAM 1                     // 防衛軍のビームの画面上の最大数

#define L_I_BEAM 10                    // インベーダー軍のビームの長さ
#define V_I_BEAM 0.8                   // インベーダー軍のビームの速度
#define P_I_BEAM 500                   // インベーダー軍のビームの初期発射確率

#define N_I_BEAM 20                    // インベーダー軍のビームの画面上の最大数
#define NXIV 9                         // インベーダー軍の列の数
#define NYIV 5                         // インベーダー軍の行の数
#define V_INVADER 0.1                  // インベーダー軍の速度

#define NOT_DECIDE 0
#define INVADER 1
#define HUMAN 2

typedef struct{            // ビットマップファイルを読み込むための構造体
  unsigned short file_type;
  unsigned long  file_size;
  unsigned short file_reserved1;
  unsigned short file_reserved2;
  unsigned long  file_offset;
  unsigned long  info_size;
  long           info_width;
  long           info_height;
  unsigned short info_planes;
  unsigned short info_bit_count;
  unsigned long  info_compression;
  unsigned long  info_size_image;
  long           info_x_pix_per_meter;
  long           info_y_pix_per_meter;
  unsigned long  info_n_palett;
  unsigned long  info_index_palett;
  unsigned char  *pixell;
}BitmapFileData;

typedef struct{              // インベーダーの状態を格納する構造体 
  unsigned char status;                     // 0:dead 1:alive
  double x, y;                              // 中心座標
  BitmapFileData *type[2];                  // ビットマップを示すポインター
}invader;


typedef struct{              // 地球防衛軍のビームの状態を格納する構造体 
  char status;                              // 0:格納庫 1:砲台の上 2:移動中
  double x;                                 // ビームのx座標
  double y0, y1;                            // ビームのy座標 y0:先頭 y1:最後尾
  double vy;                                // ビームの速度
}beam;

//---- プロトタイプ宣言 -----
void initialize(void);                      // 初期化

void draw(void);                            // 図を描く
void draw_result(void);                     // 結果表示
void draw_hodai(void);                      // 防衛軍の砲台の描画
void draw_e_beam(void);                     // 防衛軍のビームの描画
void draw_i_beam(void);                     // インベーダー軍のビームの描画
void draw_invader(void);                    // インベーダー軍の描画

void change_state(void);                    // 状態変化に関する処理
void state_e_beam(void);                    // 防衛軍のビームの状態変化
void state_invader(void);                   // インベーダー軍の状態変化
void state_i_beam(void);                    // インベーダー軍のビーム状態変化

void mouse_xy(int x, int y);
void shoot(unsigned char key, int x, int y); // 防衛軍ビーム発射
void resize(int w, int h);                  // サイズの調整
int read_bitmap(char *file_name, BitmapFileData *pict);

//---- グローバル変数 -------
double xc = 100.0;                          // マウスのx座標

invader invd[NXIV][NYIV];                   // インベーダー
int alive_inv=NXIV*NYIV;                    // 生きているインベーダーの数
double inv_vx=V_INVADER;                    // インベーダーの横方向の速度

beam e_beam[N_E_BEAM];                      // 地球防衛軍のビーム
beam *p_e_beam1;                            // 地球防衛軍の次に発射可能なビーム
beam i_beam[N_I_BEAM];                      // インベーダー軍のビーム

BitmapFileData invader11, invader12;        // ビットマップファイルのデータ
BitmapFileData invader21, invader22;
BitmapFileData invader31, invader32;
double total_mv_x=0;                        // インベーダーの型を選択

int winner = NOT_DECIDE;
char *win="You won a game.";
char *lost="You lost a game.";
//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{

  initialize();
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(W_WIN,H_WIN);          // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);   // 表示モードの指定
  glutCreateWindow("space invader modoki");       // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glutIdleFunc(change_state);               // 暇なときに実行(状態の変化)
  glutPassiveMotionFunc(mouse_xy);          // マウスイベント(砲台の移動)
  glutKeyboardFunc(shoot);                  // キーボードイベント(ビームを発射)
  glClearColor(0.0, 0.0, 0.0, 1.0);         //赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 初期化
//====================================================================
void initialize(void)
{
  int i, j;

  srand((unsigned int)time(NULL));        // 乱数を発生させるため

  for(i=0; i<N_E_BEAM; i++){
    e_beam[i].status=0;
    e_beam[i].y0=H_HODAI+L_E_BEAM;
    e_beam[i].y1=H_HODAI;
    e_beam[i].vy=0.0;
  }

  e_beam[0].status=1;                       // 砲台にのせる
  p_e_beam1=&e_beam[0];

  for(i=0; i<N_I_BEAM; i++){
    i_beam[i].status = 0;
    i_beam[i].y0 = 0;
    i_beam[i].y1 = 0;
    i_beam[i].vy = V_I_BEAM;
  }

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      invd[i][j].status=1;
      invd[i][j].x = 20*(i+1);            // x,yとも20ピクセル間隔
      invd[i][j].y = H_WIN - NYIV*20+10+20*j;
      if(j==0 || j==1){
	invd[i][j].type[0]=&invader11;
	invd[i][j].type[1]=&invader12;
      }else if(j==2 || j==3){
	invd[i][j].type[0]=&invader21;
	invd[i][j].type[1]=&invader22;
      }else{
	invd[i][j].type[0]=&invader31;
	invd[i][j].type[1]=&invader32;
      }
    }
  }

  read_bitmap("invader_11.bmp", &invader11);  // キャラクター読み込み
  read_bitmap("invader_12.bmp", &invader12);
  read_bitmap("invader_21.bmp", &invader21);
  read_bitmap("invader_22.bmp", &invader22);
  read_bitmap("invader_31.bmp", &invader31);
  read_bitmap("invader_32.bmp", &invader32);

}


//====================================================================
// 図を描く
//====================================================================
void draw(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  if(winner != NOT_DECIDE) draw_result();

  draw_hodai();         // 砲台を描く関数呼び出し
  draw_e_beam();        // 地球防衛軍のビームを描く関数の呼び出し
  draw_i_beam();        // インベーダー軍のビームを描く関数の呼び出し
  draw_invader();       // インベーダーを描く関数の呼び出し
  
  glutSwapBuffers();    // 描画
}


//====================================================================
// 勝者の表示
//====================================================================
void draw_result(void)
{
    int i=0;

    glColor3d(0.0, 1.0, 0.0);

    if(winner==HUMAN){
      while(win[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,win[i]);
	i++;
      }
    }else if(winner==INVADER){
      while(lost[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,lost[i]);
	i++;
      }
    }
}


//====================================================================
// 地球防衛軍の砲台の描画
//====================================================================
void draw_hodai(void)
{
  glColor3d(0.5, 1.0, 0.5);            // 線の色指定(RGB)
  glBegin(GL_POLYGON);
  glVertex2d(xc-W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, H_HODAI);
  glVertex2d(xc-W2_HODAI, H_HODAI);
  glEnd();
}


//====================================================================
// 地球防衛軍のビーム砲の描画
//====================================================================
void draw_e_beam(void)
{
  int i;

  for(i=0;i<N_E_BEAM;i++){
    if(e_beam[i].status != 0){
      glColor3d(1.0, 0.0, 0.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(e_beam[i].x, e_beam[i].y0);
      glVertex2d(e_beam[i].x, e_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍のビームの描画
//====================================================================
void draw_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status == 2){
      glColor3d(0.0, 0.0, 1.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(i_beam[i].x, i_beam[i].y0);
      glVertex2d(i_beam[i].x, i_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍の描画
//====================================================================
void draw_invader(void)
{
  int i, j;               // インベーダーのi列j行
  static int t=0;           // 描画するインベーダーの形

  if(10<total_mv_x){      // 10ピクセル毎に0と1に変化
    total_mv_x=0;
    t++;                  // 1増加させたtを,2で割った余り
    t=t%2;
  }

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){    // 生きているインベーダーのみを描く
	glRasterPos2d(invd[i][j].x-8,invd[i][j].y-6);     // 画像の左下座標
	glDrawPixels(invd[i][j].type[0]->info_width,      // 描画
		     invd[i][j].type[0]->info_height,
		     GL_RGBA, GL_UNSIGNED_BYTE, invd[i][j].type[t]->pixell);
	
      }
    }
  }

}


//====================================================================
// リサイズ
//     この関数は window のサイズが変化したら呼び出される
//     引数
//            w:ウィンドウの幅
//            h:ウィンドウの高さ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();                   // 変換行列を単位行列に
  gluOrtho2D(0, W_WIN, 0, H_WIN);     // world座標系の範囲
  glViewport(0, 0, w, h);             // ウィンドウ座標系を指定
}


//====================================================================
// PCが暇なときに実行する.これが実行されると状態が変化する
//====================================================================
void change_state(void)
{

  if(winner == NOT_DECIDE){
    state_e_beam();     // 地球防衛軍のビームの処理
    state_invader();    // インベーダー軍の処理
    state_i_beam();     // インベーダー軍のビームの処理
  }

  glutPostRedisplay();
}


//====================================================================
// 地球防衛軍のビームの状態の処理
//====================================================================
void state_e_beam(void)
{
  int i,l,m;
  int st0=0;
  int rdy=0;
  int nshoot=0;                 // 発射済みの地球防衛軍の玉の数
  double min_y=H_WIN+L_E_BEAM;   // 最もしたのミサイルの先のy座標
  double ydis;                  // 最も下のミサイルと発射台の距離

  for(i=0; i<N_E_BEAM; i++){
    switch(e_beam[i].status){

    //--------  格納庫にあるビームの処理 ------------------------
    case 0:
      st0=i;                    // 次に発射可能なビームを設定
      break;

    //--------  砲台にあるビームの処理 ------------------------
    case 1:
      e_beam[i].x = xc;         // x方向に移動
      rdy=1;                    // 砲台にビームがあることを示すフラグをON
      break;

    //--------  すでに発射されたビームの処理 ------------------------
    case 2:
      nshoot++;                         // 発射されているビームをカウント
      e_beam[i].y0 += e_beam[i].vy;     // ビームの移動
      e_beam[i].y1 += e_beam[i].vy;

      // ------ インベーダーにビームが衝突したことを確認して処理 ------
      for(l=0; l<NXIV; l++){    
	for(m=0; m<NYIV; m++){
	  if(invd[l][m].status==1){
	    if((invd[l][m].x-8 < e_beam[i].x) &&
	       (e_beam[i].x < invd[l][m].x+8) &&
	       (invd[l][m].y-4 < e_beam[i].y0) &&
	       (e_beam[i].y1 < invd[l][m].y+4)){
	      invd[l][m].status=0;            // インベーダーの死亡
	      alive_inv--;                    // 生きているインベーダーの数を-1
	      if(alive_inv==0)winner=HUMAN;
	      e_beam[i].status=0;             // ビームは格納庫へ
	      e_beam[i].y0=H_HODAI+L_E_BEAM;  // ビームの初期化
	      e_beam[i].y1=H_HODAI;
	    }
	  }
	}      
      }


      // ---- 画面から地球防衛軍のビームがはみ出た場合の処理 --------
      if(H_WIN+L_E_BEAM < e_beam[i].y0){
	e_beam[i].status = 0;
	e_beam[i].y0 = H_HODAI+L_E_BEAM;
	e_beam[i].y1 = H_HODAI;
	e_beam[i].vy = 0.0;
      }
      if(e_beam[i].y0 < min_y) min_y=e_beam[i].y0;
      break;
    default:
      printf("e_beam status error!!\n");
      exit(1);
    }
  }


  // --- 地球防衛軍の新たな発射可能なビームの処理 -----
  ydis = min_y-H_HODAI;
  if( (2.5*L_E_BEAM < ydis) && (rdy==0) && (nshoot<N_E_BEAM) ){
    e_beam[st0].status=1;
    p_e_beam1=(beam *)&e_beam[st0];     // 発射可能なビームをポインターで表現
  }
}


//====================================================================
// インベーダー軍の状態の処理
//====================================================================
void state_invader(void)
{
  int i, j, k;
  double ivmin_x=W_WIN, ivmax_x=0;
  double ivmin_y=H_WIN, ivmax_y=0;
  int can_attack;

  total_mv_x += fabs(inv_vx);     // キャラクターを変化させるときに使う
  for(i=0; i<NXIV; i++){
    can_attack=1;
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){   // インベーダーの生死のチェック
	invd[i][j].x += inv_vx;   // インベーダーの横方向移動
	// ---- インベーダー軍のビーム発射の処理 ------
	if(can_attack == 1 && rand()%P_I_BEAM == 0){  // 発射条件
	  for(k=0; k<N_I_BEAM; k++){
	    if(i_beam[k].status !=2){      // 発射可能なビームを探す
	      i_beam[k].status =2;         // ビームの発射
	      i_beam[k].x = invd[i][j].x;
	      i_beam[k].y0 = invd[i][j].y;
	      i_beam[k].y1 = invd[i][j].y-L_I_BEAM;
	      break;
	    }
	  }
	}
	// --- インベーダー軍の左右上下の端の座標 -------
	if(invd[i][j].x < ivmin_x) ivmin_x=invd[i][j].x;   // 左端 
	if(invd[i][j].x > ivmax_x) ivmax_x=invd[i][j].x;   // 右端
	if(invd[i][j].y < ivmin_y) ivmin_y=invd[i][j].y;   // 下の端
	if(invd[i][j].y > ivmax_y) ivmax_y=invd[i][j].y;   // 上の端
	can_attack=0;
      }
    }
  }


  if(ivmin_x < 10) inv_vx = V_INVADER;           // 左端に達したとき
  if(ivmax_x > W_WIN-10) inv_vx = -V_INVADER;    // 右端に達したとき
  
  if((ivmin_x < 10) || (ivmax_x > W_WIN-10)){    // 左右の端に達しとき
    for(i=0; i<NXIV; i++){
      for(j=0; j<NYIV; j++){
	invd[i][j].y -= 10;                       // 下に降りる
      }
    }
  }
}


//====================================================================
// インベーダー軍のビームの状態の処理
//====================================================================
void state_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status ==2){
      i_beam[i].y0 -= i_beam[i].vy;
      i_beam[i].y1 -= i_beam[i].vy;
   
      if(i_beam[i].y1 < 0) i_beam[i].status=0;

      if((xc-W2_HODAI < i_beam[i].x) &&
	 (i_beam[i].x < xc+W2_HODAI) &&
	 (L_HODAI < i_beam[i].y0) &&
	 (i_beam[i].y1 < H_HODAI)){
	winner=INVADER;            // 地球防衛軍の負け
      }
    }
  }
}


//====================================================================
// マウスイベントの処理
//====================================================================
void mouse_xy(int x, int y)
{
  xc=x;                // マウスのx座標をグローバル変数の xc へ代入
}


//====================================================================
// キーボードイベントの処理
// スペースキーが押されたら地球防衛軍のビームを発射
//====================================================================
void shoot(unsigned char key, int x, int y)
{
  //--- スペースキーが押されて,発射可能なビームがあるとき ----
  if(key==' ' && p_e_beam1 != NULL){
    p_e_beam1->status = 2;            // ビームを発射の状態にする
    p_e_beam1->vy = V_E_BEAM;         // ビームの速度を設定
    p_e_beam1 = NULL;                 // 発射可能なビームが無い
  }
}


/*=======================================================================*/
/*  set bitmap file datas to memory from disk                            */
/*=======================================================================*/
int read_bitmap(char *file_name, BitmapFileData *pict)
{
  unsigned int lb;
  unsigned int i,j;
  unsigned int position;
  FILE *fp;

  fp=fopen(file_name,"r");

  /*----- read file header  --------------*/
  fread(&(pict->file_type), 2, 1, fp);
  fread(&(pict->file_size), 4, 1, fp);
  fread(&(pict->file_reserved1), 2, 1, fp);
  fread(&(pict->file_reserved2), 2, 1, fp);
  fread(&(pict->file_offset), 4, 1, fp);

  /*----- read info header  --------------*/
  fread(&(pict->info_size), 4, 1, fp);
  fread(&(pict->info_width), 4, 1, fp);
  fread(&(pict->info_height), 4, 1, fp);
  fread(&(pict->info_planes), 2, 1, fp);
  fread(&(pict->info_bit_count), 2, 1, fp);
  fread(&(pict->info_compression), 4, 1, fp);
  fread(&(pict->info_size_image), 4, 1, fp);
  fread(&(pict->info_x_pix_per_meter), 4, 1, fp);
  fread(&(pict->info_y_pix_per_meter), 4, 1, fp);
  fread(&(pict->info_n_palett), 4, 1, fp);
  fread(&(pict->info_index_palett), 4, 1, fp);

  /*----- read the bitmap pixel datas  --------------*/

  pict->pixell=calloc(4*pict->info_width*pict->info_height, sizeof(unsigned char));

  /* lb: byte/holizontal line  */
  lb = (pict->info_width * pict->info_bit_count) / 8;

  if ((lb % 4) != 0) {
    lb = ((lb / 4) + 1) * 4;                 /* adjust 4byte */
  }


  for(i = 0; i < pict->info_height; i++){
    position = pict->file_offset + lb * i;
    fseek(fp, position, SEEK_SET);
    for(j = 0; j < pict->info_width; j++){
      *(pict->pixell+i*pict->info_width*4+j*4+3)=0xff;    // 透明度
      fread(pict->pixell+i*pict->info_width*4+j*4+2, 1, 1, fp);
      fread(pict->pixell+i*pict->info_width*4+j*4+1, 1, 1, fp);
      fread(pict->pixell+i*pict->info_width*4+j*4+0, 1, 1, fp);
    }
  }

  fclose(fp);

  return 0;
}

ゲームの得点を表示したい

ゲームの得点を表示したいのですが,方法がわかりません.

「この程度のことは自分で考えてください」と言いたいのですが,できな人もいるので簡単な例を示します.得点を表示するためには,得点を計算することとそれを表示することが必要です.「得点をどのようにして決めるか?」と言うことを最初に決める必要があります.ここでのプログラム例では,次のようにしました.

  • ビームを発射する毎に-1点とします.
  • やっつけたインベーダーに応じて,10点あるいは20点,30点とします.

最初の部分は,ビームを発射する毎に現在の得点を-1します.インベーダーを撃退したときの得点は,衝突判定の時に点数を加算します.インベーダーの情報を表す構造体にそのインベーダーの得点を記憶させておけば良いでしょう.得点はグローバル変数にして,ゼロに初期化しておきます.

表示の方法は,このページの「文字はどのようにして表示させるのですか?」の項を参考にしてください.

プログラムは次のようになります.ただし,このプログラムを実行するときには,インベーダーのキャラクターのビットマップファイル(1, 2, 3, 4, 5, 6)が必要です.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <GL/glut.h>

#define H_WIN 400                      // ウィンドウの幅
#define W_WIN 300                      // ウィンドウの高さ

#define W2_HODAI 10                    // 砲台の横幅の半分
#define H_HODAI 15                     // 砲台の上面のy座標
#define L_HODAI 5                      // 砲台の下面のy座標
#define L_E_BEAM 20                    // 防衛軍のビームの長さ
#define V_E_BEAM 1.5                   // 防衛軍のビームの速度
#define N_E_BEAM 1                     // 防衛軍のビームの画面上の最大数

#define L_I_BEAM 10                    // インベーダー軍のビームの長さ
#define V_I_BEAM 0.8                   // インベーダー軍のビームの速度
#define P_I_BEAM 500                   // インベーダー軍のビームの初期発射確率

#define N_I_BEAM 20                    // インベーダー軍のビームの画面上の最大数
#define NXIV 9                         // インベーダー軍の列の数
#define NYIV 5                         // インベーダー軍の行の数
#define V_INVADER 0.1                  // インベーダー軍の速度

#define NOT_DECIDE 0
#define INVADER 1
#define HUMAN 2

typedef struct{            // ビットマップファイルを読み込むための構造体
  unsigned short file_type;
  unsigned long  file_size;
  unsigned short file_reserved1;
  unsigned short file_reserved2;
  unsigned long  file_offset;
  unsigned long  info_size;
  long           info_width;
  long           info_height;
  unsigned short info_planes;
  unsigned short info_bit_count;
  unsigned long  info_compression;
  unsigned long  info_size_image;
  long           info_x_pix_per_meter;
  long           info_y_pix_per_meter;
  unsigned long  info_n_palett;
  unsigned long  info_index_palett;
  unsigned char  *pixell;
}BitmapFileData;

typedef struct{              // インベーダーの状態を格納する構造体 
  unsigned char status;                     // 0:dead 1:alive
  double x, y;                              // 中心座標
  BitmapFileData *type[2];                  // ビットマップを示すポインター
  int point;                                // 得点
}invader;


typedef struct{              // 地球防衛具運のビームの状態を格納する構造体 
  char status;                              // 0:格納庫 1:砲台の上 2:移動中
  double x;                                 // ビームのx座標
  double y0, y1;                            // ビームのy座標 y0:先頭 y1:最後尾
  double vy;                                // ビームの速度
}beam;

//---- プロトタイプ宣言 -----
void initialize(void);                      // 初期化

void draw(void);                            // 図を描く
void draw_result(void);                     // 結果表示
void draw_hodai(void);                      // 防衛軍の砲台の描画
void draw_e_beam(void);                     // 防衛軍のビームの描画
void draw_i_beam(void);                     // インベーダー軍のビームの描画
void draw_invader(void);                    // インベーダー軍の描画
void show_message(void);                    // 得点などを表示

void change_state(void);                    // 状態変化に関する処理
void state_e_beam(void);                    // 防衛軍のビームの状態変化
void state_invader(void);                   // インベーダー軍の状態変化
void state_i_beam(void);                    // インベーダー軍のビーム状態変化

void mouse_xy(int x, int y);
void shoot(unsigned char key, int x, int y); // 防衛軍ビーム発射
void resize(int w, int h);                  // サイズの調整
int read_bitmap(char *file_name, BitmapFileData *pict);

//---- グローバル変数 -------
double xc = 100.0;                          // マウスのx座標

invader invd[NXIV][NYIV];                   // インベーダー
int alive_inv=NXIV*NYIV;                    // 生きているインベーダーの数
double inv_vx=V_INVADER;                    // インベーダーの横方向の速度

beam e_beam[N_E_BEAM];                      // 地球防衛軍のビーム
beam *p_e_beam1;                            // 地球防衛軍の次に発射可能なビーム
beam i_beam[N_I_BEAM];                      // インベーダー軍のビーム

BitmapFileData invader11, invader12;        // ビットマップファイルのデータ
BitmapFileData invader21, invader22;        
BitmapFileData invader31, invader32;
double total_mv_x=0;                        // インベーダーの型を選択

int winner = NOT_DECIDE;
char *win="You won a game.";
char *lost="You lost a game.";

int score=0;                                // スコアー

//====================================================================
// main関数
//====================================================================
int main(int argc, char *argv[])
{

  initialize();
  glutInitWindowPosition(100,200);          // 初期位置(x,y)指定
  glutInitWindowSize(W_WIN,H_WIN);          // 初期サイズ(幅,高さ)指定
  glutInit(&argc, argv);                    // GLUT 初期化
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);   // 表示モードの指定
  glutCreateWindow("space invader modoki");       // windowをタイトルを付けてを開く
  glutDisplayFunc(draw);                    // イベントにより呼び出し
  glutReshapeFunc(resize);                  // サイズ変更のときに呼び出す関数指定
  glutIdleFunc(change_state);               // 暇なときに実行(状態の変化)
  glutPassiveMotionFunc(mouse_xy);          // マウスイベント(砲台の移動)
  glutKeyboardFunc(shoot);                  // キーボードイベント(ビームを発射)
  glClearColor(0.0, 0.0, 0.0, 1.0);         //赤緑青と透明度
  glutMainLoop();                           // GLUTの無限ループ

  return 0;
}

//====================================================================
// 初期化
//====================================================================
void initialize(void)
{
  int i, j;

  srand((unsigned int)time(NULL));        // 乱数を発生させるため

  for(i=0; i<N_E_BEAM; i++){
    e_beam[i].status=0;
    e_beam[i].y0=H_HODAI+L_E_BEAM;
    e_beam[i].y1=H_HODAI;
    e_beam[i].vy=0.0;
  }

  e_beam[0].status=1;                       // 砲台にのせる
  p_e_beam1=&e_beam[0];

  for(i=0; i<N_I_BEAM; i++){
    i_beam[i].status = 0;
    i_beam[i].y0 = 0;
    i_beam[i].y1 = 0;
    i_beam[i].vy = V_I_BEAM;
  }

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      invd[i][j].status=1;
      invd[i][j].x = 20*(i+1);            // x,yとも20ピクセル間隔
      invd[i][j].y = H_WIN - NYIV*20+10+20*j-20;
      if(j==0 || j==1){
	invd[i][j].type[0]=&invader11;
	invd[i][j].type[1]=&invader12;
	invd[i][j].point=10;
      }else if(j==2 || j==3){
	invd[i][j].type[0]=&invader21;
	invd[i][j].type[1]=&invader22;
	invd[i][j].point=20;
      }else{
	invd[i][j].type[0]=&invader31;
	invd[i][j].type[1]=&invader32;
	invd[i][j].point=30;
      }
    }
  }

  read_bitmap("invader_11.bmp", &invader11);  // キャラクター読み込み
  read_bitmap("invader_12.bmp", &invader12);
  read_bitmap("invader_21.bmp", &invader21);
  read_bitmap("invader_22.bmp", &invader22);
  read_bitmap("invader_31.bmp", &invader31);
  read_bitmap("invader_32.bmp", &invader32);

}


//====================================================================
// 図を描く
//====================================================================
void draw(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  if(winner != NOT_DECIDE) draw_result();

  draw_hodai();         // 砲台を描く関数呼び出し
  draw_e_beam();        // 地球防衛軍のビームを描く関数の呼び出し
  draw_i_beam();        // インベーダー軍のビームを描く関数の呼び出し
  draw_invader();       // インベーダーを描く関数の呼び出し
  show_message();      // 得点などを表示
  
  glutSwapBuffers();    // 描画
}


//====================================================================
// 勝者の表示
//====================================================================
void draw_result(void)
{
    int i=0;

    glColor3d(0.0, 1.0, 0.0);

    if(winner==HUMAN){
      while(win[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,win[i]);
	i++;
      }
    }else if(winner==INVADER){
      while(lost[i]!='\0'){
	glRasterPos2i(50+15*i,100);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,lost[i]);
	i++;
      }
    }
}


//====================================================================
// 地球防衛軍の砲台の描画
//====================================================================
void draw_hodai(void)
{
  glColor3d(0.5, 1.0, 0.5);            // 線の色指定(RGB)
  glBegin(GL_POLYGON);
  glVertex2d(xc-W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, L_HODAI);
  glVertex2d(xc+W2_HODAI, H_HODAI);
  glVertex2d(xc-W2_HODAI, H_HODAI);
  glEnd();
}


//====================================================================
// 地球防衛軍のビーム砲の描画
//====================================================================
void draw_e_beam(void)
{
  int i;

  for(i=0;i<N_E_BEAM;i++){
    if(e_beam[i].status != 0){
      glColor3d(1.0, 0.0, 0.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(e_beam[i].x, e_beam[i].y0);
      glVertex2d(e_beam[i].x, e_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍のビームの描画
//====================================================================
void draw_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status == 2){
      glColor3d(0.0, 0.0, 1.0);            // 線の色指定(RGB)
      glBegin(GL_LINES);
      glVertex2d(i_beam[i].x, i_beam[i].y0);
      glVertex2d(i_beam[i].x, i_beam[i].y1);
      glEnd();
    }
  }
}


//====================================================================
// インベーダー軍の描画
//====================================================================
void draw_invader(void)
{
  int i, j;               // インベーダーのi列j行
  static int t=0;           // 描画するインベーダーの形

  if(10<total_mv_x){      // 10ピクセル毎に0と1に変化
    total_mv_x=0;
    t++;                  // 1増加させたtを,2で割った余り
    t=t%2;
  }

  for(i=0; i<NXIV; i++){
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){    // 生きているインベーダーのみを描く
	glRasterPos2d(invd[i][j].x-8,invd[i][j].y-6);     // 画像の左下座標
	glDrawPixels(invd[i][j].type[0]->info_width,      // 描画
		     invd[i][j].type[0]->info_height, 
		     GL_RGBA, GL_UNSIGNED_BYTE, invd[i][j].type[t]->pixell);  
	
      }
    }
  }

}

//====================================================================
// スコアなどを表示
//====================================================================
void show_message(void)
{
  int i;
  char msg_score[256];

  sprintf(msg_score, "Space Invader     score:%d", score);

  glColor3f(0, 1, 0);                         // 色設定
  glRasterPos2d(10, H_WIN-15);                // 文字の場所
  for(i=0; msg_score[i]!='\0'; i++){
    glutBitmapCharacter(GLUT_BITMAP_9_BY_15, msg_score[i]);
  }

}

//====================================================================
// リサイズ
//     この関数は window のサイズが変化したら呼び出される
//     引数
//            w:ウィンドウの幅
//            h:ウィンドウの高さ
//====================================================================
void resize(int w, int h)
{
  glLoadIdentity();                   // 変換行列を単位行列に
  gluOrtho2D(0, W_WIN, 0, H_WIN);     // world座標系の範囲
  glViewport(0, 0, w, h);             // ウィンドウ座標系を指定
}


//====================================================================
// PCが暇なときに実行する.これが実行されると状態が変化する
//====================================================================
void change_state(void)
{

  if(winner == NOT_DECIDE){
    state_e_beam();     // 地球防衛軍のビームの処理
    state_invader();    // インベーダー軍の処理
    state_i_beam();     // インベーダー軍のビームの処理
  }

  glutPostRedisplay();
}


//====================================================================
// 地球防衛軍のビームの状態の処理
//====================================================================
void state_e_beam(void)
{
  int i,l,m;
  int st0=0;
  int rdy=0;
  int nshoot=0;                 // 発射済みの地球防衛軍の玉の数
  double min_y=H_WIN+L_E_BEAM;   // 最もしたのミサイルの先のy座標
  double ydis;                  // 最も下のミサイルと発射台の距離

  for(i=0; i<N_E_BEAM; i++){
    switch(e_beam[i].status){

    //--------  格納庫にあるビームの処理 ------------------------
    case 0:
      st0=i;                    // 次に発射可能なビームを設定
      break;

    //--------  砲台にあるビームの処理 ------------------------
    case 1:
      e_beam[i].x = xc;         // x方向に移動
      rdy=1;                    // 砲台にビームがあることを示すフラグをON
      break;

    //--------  すでに発射されたビームの処理 ------------------------
    case 2:
      nshoot++;                         // 発射されているビームをカウント
      e_beam[i].y0 += e_beam[i].vy;     // ビームの移動
      e_beam[i].y1 += e_beam[i].vy;

      // ------ インベーダーにビームが衝突したことを確認して処理 ------
      for(l=0; l<NXIV; l++){    
	for(m=0; m<NYIV; m++){
	  if(invd[l][m].status==1){
	    if((invd[l][m].x-8 < e_beam[i].x) &&
	       (e_beam[i].x < invd[l][m].x+8) &&
	       (invd[l][m].y-4 < e_beam[i].y0) &&
	       (e_beam[i].y1 < invd[l][m].y+4)){
	      invd[l][m].status=0;            // インベーダーの死亡
	      alive_inv--;                    // 生きているインベーダーの数を-1
	      score+=invd[l][m].point;
	      if(alive_inv==0)winner=HUMAN;
	      e_beam[i].status=0;             // ビームは格納庫へ
	      e_beam[i].y0=H_HODAI+L_E_BEAM;  // ビームの初期化
	      e_beam[i].y1=H_HODAI;
	    }
	  }
	}      
      }


      // ---- 画面から地球防衛軍のビームがはみ出た場合の処理 --------
      if(H_WIN+L_E_BEAM < e_beam[i].y0){
	e_beam[i].status = 0;
	e_beam[i].y0 = H_HODAI+L_E_BEAM;
	e_beam[i].y1 = H_HODAI;
	e_beam[i].vy = 0.0;
      }
      if(e_beam[i].y0 < min_y) min_y=e_beam[i].y0;
      break;
    default:
      printf("e_beam status error!!\n");
      exit(1);
    }
  }


  // --- 地球防衛軍の新たな発射可能なビームの処理 -----
  ydis = min_y-H_HODAI;
  if( (2.5*L_E_BEAM < ydis) && (rdy==0) && (nshoot<N_E_BEAM) ){
    e_beam[st0].status=1;
    p_e_beam1=(beam *)&e_beam[st0];     // 発射可能なビームをポインターで表現
  }
}


//====================================================================
// インベーダー軍の状態の処理
//====================================================================
void state_invader(void)
{
  int i, j, k;
  double ivmin_x=W_WIN, ivmax_x=0;
  double ivmin_y=H_WIN, ivmax_y=0;
  int can_attack;

  total_mv_x += fabs(inv_vx);     // キャラクターを変化させるときに使う
  for(i=0; i<NXIV; i++){
    can_attack=1;
    for(j=0; j<NYIV; j++){
      if(invd[i][j].status==1){   // インベーダーの生死のチェック
	invd[i][j].x += inv_vx;   // インベーダーの横方向移動
	// ---- インベーダー軍のビーム発射の処理 ------
	if(can_attack == 1 && rand()%P_I_BEAM == 0){  // 発射条件
	  for(k=0; k<N_I_BEAM; k++){
	    if(i_beam[k].status !=2){      // 発射可能なビームを探す
	      i_beam[k].status =2;         // ビームの発射
	      i_beam[k].x = invd[i][j].x;
	      i_beam[k].y0 = invd[i][j].y;
	      i_beam[k].y1 = invd[i][j].y-L_I_BEAM;
	      break;
	    }
	  }
	}
	// --- インベーダー軍の左右上下の端の座標 -------
	if(invd[i][j].x < ivmin_x) ivmin_x=invd[i][j].x;   // 左端 
	if(invd[i][j].x > ivmax_x) ivmax_x=invd[i][j].x;   // 右端
	if(invd[i][j].y < ivmin_y) ivmin_y=invd[i][j].y;   // 下の端
	if(invd[i][j].y > ivmax_y) ivmax_y=invd[i][j].y;   // 上の端
	can_attack=0;
      }
    }
  }


  if(ivmin_x < 10) inv_vx = V_INVADER;           // 左端に達したとき
  if(ivmax_x > W_WIN-10) inv_vx = -V_INVADER;    // 右端に達したとき
  
  if((ivmin_x < 10) || (ivmax_x > W_WIN-10)){    // 左右の端に達しとき
    for(i=0; i<NXIV; i++){
      for(j=0; j<NYIV; j++){
	invd[i][j].y -= 10;                       // 下に降りる
      }
    }
  }
}


//====================================================================
// インベーダー軍のビームの状態の処理
//====================================================================
void state_i_beam(void)
{
  int i;

  for(i=0; i<N_I_BEAM; i++){
    if(i_beam[i].status ==2){
      i_beam[i].y0 -= i_beam[i].vy;
      i_beam[i].y1 -= i_beam[i].vy;
   
      if(i_beam[i].y1 < 0) i_beam[i].status=0;

      if((xc-W2_HODAI < i_beam[i].x) &&
	 (i_beam[i].x < xc+W2_HODAI) &&
	 (L_HODAI < i_beam[i].y0) &&
	 (i_beam[i].y1 < H_HODAI)){
	winner=INVADER;            // 地球防衛軍の負け
      }
    }
  }
}


//====================================================================
// マウスイベントの処理
//====================================================================
void mouse_xy(int x, int y)
{
  xc=x;                // マウスのx座標をグローバル変数の xc へ代入
}


//====================================================================
// キーボードイベントの処理
// スペースキーが押されたら地球防衛軍のビームを発射
//====================================================================
void shoot(unsigned char key, int x, int y)
{
  //--- スペースキーが押されて,発射可能なビームがあるとき ----
  if(key==' ' && p_e_beam1 != NULL){
    p_e_beam1->status = 2;            // ビームを発射の状態にする
    p_e_beam1->vy = V_E_BEAM;         // ビームの速度を設定
    p_e_beam1 = NULL;                 // 発射可能なビームが無い
    score--;                          // ビームを発射すると -1 点
  }
}


/*=======================================================================*/
/*  set bitmap file datas to memory from disk                            */
/*=======================================================================*/
int read_bitmap(char *file_name, BitmapFileData *pict)
{
  unsigned int lb;
  unsigned int i,j;
  unsigned int position;
  FILE *fp;

  fp=fopen(file_name,"r");

  /*----- read file header  --------------*/
  fread(&(pict->file_type), 2, 1, fp);
  fread(&(pict->file_size), 4, 1, fp);
  fread(&(pict->file_reserved1), 2, 1, fp);
  fread(&(pict->file_reserved2), 2, 1, fp);
  fread(&(pict->file_offset), 4, 1, fp);

  /*----- read info header  --------------*/
  fread(&(pict->info_size), 4, 1, fp);
  fread(&(pict->info_width), 4, 1, fp);
  fread(&(pict->info_height), 4, 1, fp);
  fread(&(pict->info_planes), 2, 1, fp);
  fread(&(pict->info_bit_count), 2, 1, fp);
  fread(&(pict->info_compression), 4, 1, fp);
  fread(&(pict->info_size_image), 4, 1, fp);
  fread(&(pict->info_x_pix_per_meter), 4, 1, fp);
  fread(&(pict->info_y_pix_per_meter), 4, 1, fp);
  fread(&(pict->info_n_palett), 4, 1, fp);
  fread(&(pict->info_index_palett), 4, 1, fp);

  /*----- read the bitmap pixel datas  --------------*/

  pict->pixell=calloc(4*pict->info_width*pict->info_height, sizeof(unsigned char));

  /* lb: byte/holizontal line  */
  lb = (pict->info_width * pict->info_bit_count) / 8;

  if ((lb % 4) != 0) {
    lb = ((lb / 4) + 1) * 4;                 /* adjust 4byte */
  }


  for(i = 0; i < pict->info_height; i++){
    position = pict->file_offset + lb * i;
    fseek(fp, position, SEEK_SET);
    for(j = 0; j < pict->info_width; j++){
      *(pict->pixell+i*pict->info_width*4+j*4+3)=0xff;    // 透明度
      fread(pict->pixell+i*pict->info_width*4+j*4+2, 1, 1, fp);
      fread(pict->pixell+i*pict->info_width*4+j*4+1, 1, 1, fp);
      fread(pict->pixell+i*pict->info_width*4+j*4+0, 1, 1, fp);
    }
  }

  fclose(fp);

  return 0;
}

その他

1000行も書けません

1000行のプログラムなんか,とてもかけそうにありません.

1000行のプログラムが書けるようにがんばってください.ゲームのプログラムであれば,背景や絵,メッセージに凝るとか,得点の計算や表示に工夫すれば行数は稼げます.努力した後,1000行に達しなくても良いですが,プログラムは完成させてください.

DirectXを使っても良いですか?

DirectXの使用はOKです.新しいテクノロジーをどんどん使ってください.ただし,私が教えることができないことも多々ありますので,自分で調べて使うことになります.私は,DirectXについて全く知りません.

Windowsでインベーダーのプログラム

授業で使ったインベーダーのプログラムをWindowsでコンパイル,実行させたいのですが,どのようにすれば良いのでしょうか?

以下に示す手順で,授業中 Linux でコンパイル・実行させたインベーダーのプログラムをWindowsでも動作させることができます.電気棟3FのPCルームのパソコンのWindowsは,OpenGLの設定が完了しているので,実行は簡単です.

  1. 統合開発環境の起動デスクトップのMicrosoft Visual Studio .NET 2003をダブルクリックします.統合開発環境を使うと,エディタを使うプログラムの記述,コンパイルとリンク,そして実行までもが一つのプログラムの中でできるので便利です.
  2. プロジェクトの作成統合開発環境でプログラムを作成する場合,最初にプロジェクトを作成します.メインメニューのファイル新規作成プロジェクトを選択します(画面).その後,プロジェクトを以下のように設定します.
    1. 「新しいプロジェクト」の ダイアログが現れるので,次のように設定します.(画面).
      • プロジェクト名を記入(ex: invader)
      • Win32コンソールプロジェクトを選択
      • OKボタンを押す
    2. 「Win32 アプリケーション ウィザード-invader」が表示されるので,アプリケーションの設定のタグを選択して,次のように設定します(画面).
      • アプリケーションの種類:コンソールアプリケーション
      • 追加のオプション:空のプロジェクト
      • 完了ボタンを押す
  3. ソースプログラムの作成ソースファイルをプロジェクトに追加して,プログラムを書きます.
    1. メインメニューのファイル新しい項目の追加を選択する(画面).すると,「新しい項目の追加 - invader」の ダイアログが現れるので,次のように設定する(画面).
      • テンプレート(T):C++ファイル(cpp)
      • ファイル名(N):適当なファイル名.cpp (ex:invader_main.cpp)
      • 場所(L):プロジェクトファイルのある場所(デフォルトでよい)
      • 開くボタンを押す.
    2. 次のようにして,ソースプログラムを記述する.
      • インベーダーのプログラムをコピー・ペーストする.
      • ヘッダーファイルをインクルードする順序を変える.#include <GL/glut.h>を最後に(画面).
      • 場所(L):プロジェクトファイルのある場所(デフォルトでよい)
      • 開くボタンを押す.
  4. 実行ファイルの作成コンパイルとリンクを行い実行ファイルを作成することをビルドと言います.メインメニューのビルドソリューションのビルドを選択(画面)することにより,実行ファイル作成まで自動で処理されます.
  5. プログラムの実行メインメニューのデバッグデバッグなしで開始を選択.する(画面)と,プログラムが実行されます.

実行ファイルは,プロジェクトのディレクトリーにあるフォルダーDebugに作られ,プロジェクト名.exe(invader.exe)となります.これをダブルクリック,あるいはコマンドプロンプトを使って,実行させることができます.統合開発環境が無くても,プログラムの実行が可能です.

次の機会にこのプログラムを書き直したい場合,プロジェクトファイルをダブルクリック(画面)します.すると,統合開発環境が起動しますので,プログラムの編集ができます.

電気棟3FのPCルームのWindowsでは,OpenGLを使ったプログラムを容易に作成できます.これは,必要な設定を予め行っているからです.皆さんの自宅のパソコンでOpenGLを使う場合には,設定が必要です.竹下先生のWEBページを参考に設定してください.

見ることができないWEBページがあります

プログラム作成時に,参考にしたいWEBページがあります.しかし,学校のパソコンから閲覧ができません.解決方法はないでしょうか?

学校のサーバーの設定を変えることにより,閲覧可能になります.参考にしたいWEBページのURLを担当教員に教えてください.情報処理センターに設定の変更を依頼します.

教育に関係ないと思われるWEBページをフィルターにかけて閲覧できないようにしてます.例えば,シューティングゲームと言うようなキーワードでフィルタリングしています.シューティングゲームを作成する上で,どうしても参考にしたいページがあればURLを指定して解除できます.プログラム作成上,有益か否かを担当教員が判断した後,フィルターの設定依頼を行います.

ビットマップファイルへの変換方法が分かりません

JPEGやPNG等の図をビットマップファイルへ変換する方法が分かりません.どのようにすれば良いのでしょうか?

多くの画像表示/操作のソフトウェアーでフォーマットの変換ができます.PCルームのパソコンであれば,LinuxではGIMP,WindowsであればIrfanViewが使えます.元画像を読み込んで,ビットマップで保存するだけです.画像のサイズの変更もできます.



no counter