#これまでのあらすじ
- 暇だし過去に作った弾幕シューティングを思い出しながらつくる
- glutとC言語を使う
- 環境構築
0日目はここです。
#1日目
今日から頑張って弾幕シューティング作っていきます!とりあえずSolid Aetherのようなゲームを目指そうかなと。BGMはなし。そしてタイトルにもある通りC言語で開発します。決してC++ではありません笑。まあ縛りみたいなもんかな。クラスなしでできるだけオブジェクトを意識して開発してみます。とりあえず今からプログラミングにチャレンジする方のために、今流行りの言語だけじゃなくてC言語でもこれくらいはできるぞ、みたいな雰囲気を少しでもわかってもらえたらうれしいなと思ったり。実装のめんどさとか笑。
#今日の目標
- プログラムの骨格作り
- 文字列描画(英数字記号のみ)
#ゲームの流れ
- 入力
- 計算
- 描画
大雑把に言うとこの繰り返しです。なので、上の3つの処理をできるだけ切り離してこの先実装できるように、ここから骨格を作っていきます。...がんばる。
#プログラムの骨格
まず初めにこのゲームの骨格を作ってみます。ゲームプログラミングをほとんど知らない自分が何とか今ある知識を使って設計してみます。また、まずはmain関数。ここには初期化処理とゲーム実行処理のみ書くことにします。そのゲームに関する2つの処理は別のファイルに定義することにします。
#include "game.h"
int main(int argc, char *argv[]){
gameInit(&argc, argv);
gameRun();
return 0;
}
#ifndef ___HEADER_GAME
#define ___HEADER_GAME
void gameInit(int *argc, char **argv);
void gameRun();
#endif$
gameInit関数はゲーム全体の初期化、gameRun関数はゲームを実行します。
ここまで書いておいてあれですが、ここに書く説明は私が実装していったそのままの流れにします。なので、ない処理をあるものとして実装し、その後でまだない処理を実装していきます。
話をもどします。
次に上で使った関数の実体を作ります。とりあえず0日目に作った空のウィンドウを出すプログラムを目指します。gameInit関数の中でglutの初期化を行うglInit関数を呼んで、そこでglut関係の初期化することにします。gameRun関数の中ではglutMainLoop関数を呼ぶだけにします。
glInit関数の中では、まずglutの初期化、描画モード設定、ウィンドウの場所とサイズ設定、ウィンドウの名前設定、描画関数の登録を行います。ウィンドウのサイズはdefine.hの中に書くことにします。描画関数はdisplay.hに定義されるdisplayMain関数とします。
#include "game.h"
#include <GL/glut.h>
#include "define.h"
#include "display.h"
void glInit(int *argc, char **argv);
void gameInit(int *argc, char **argv){
glInit(argc, argv);
}
void gameRun(){
glutMainLoop();
}
void glInit(int *argc, char **argv){
//initialization
glutInit(argc, argv);
glutInitDisplayMode(GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutCreateWindow("myGame");
glutDisplayFunc(displayMain);
}
ifndef ___HEADER_DEFINE
#define ___HEADER_DEFINE
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#endif
#ifndef ___HEADER_DISPLAY
#define ___HEADER_DISPLAY
void displayMain();
#endif
displayMain関数にはゲームの描画処理をかいていきます。ここではまだ何もしません。
#include "display.h"
void displayMain(){
}
とりあえず骨格はこんなもんでしょうか。上に書いたゲームに必要な3つの処理のうち描画部分の処理を切り離すことができていると思います。つまり、この先いろいろなものを描画していくわけですが、それらは全てdisplayMain関数の中に書けばよいわけです。...たぶん。
手探りでやっているのでこの先変更あるかも。
それではコンパイルして実行してみましょう。といっても、毎回長ったらしいコマンドをかいて実行するのはめんどいのでMakefileもささっと書きます。Makefileあまりかいたことないのでマクロとかわかりません。
main: main.c game.o display.o
gcc main.c game.o display.o -o main -lglut -lGLU -lGL
game.o: game.c game.h define.h display.h
gcc -c game.c -o game.o -lglut -lGLU -lGL
display.o: display.c display.h
gcc -c display.c -o display.o -lglut -lGLU -lGL
clean:
rm -rf main *.o
以下のコマンドでコンパイル、実行です。
$ make
$ ./main
無事0日目と同じ実行結果になりました。
...ここまで書くのにプログラム書く時間の2倍以上かかった。
#文字列描画
よし、もう少しがんばる。黒い画面だとさみしいので、このタイミングで文字列を描画する処理も追加します。この後絶対使うし。標準出力でデバッグしてもいいんだけで、なるべくウィンドウ内にデバッグ情報を表示したいからです。
というわけでdisplayMain関数に文字列を描画する処理を加えます。文字列を描画するにはglutの描画の仕組みもいろいろ知ってなきゃいけないけどここでは省略!とりあえずfontBegin関数とfontEnd関数の間に文字列描画の処理を書くことにします。
- 文字列を描画する場所(座標)をセットする関数
- 文字列の大きさをセットする関数
- 文字の線の太さをセットする関数
- 文字列の色をセットする関数
- 文字列を渡して描画する関数
これくらい用意しておけば何とかなるはず。これらを使ってウィンドウの左上を座標(x, y) = (0, 0)として右方向をx正方向、下方向をy正方向にしたとき、ウィンドウ上の(0, 100)の場所に大きさ25、太さ1、青色で書式付き文字列を描画する処理をdisplayMain関数に書きます。どの関数がどんな処理をしているかはなるべく関数の名前で分かるようにしているつもりです。
void displayMain(){
glClear(GL_COLOR_BUFFER_BIT);
float test1 = 1.0f;
int test2 = 23;
//font test
fontBegin();
fontSetPosition(0.0, 100.0);
fontSetSize(FONT_DEFAULT_SIZE * 0.25);
fontSetWeight(1.0);
fontSetColor(0, 0, 255);
fontDraw("float:%f, int:%d", test1, test2);
fontEnd();
glutSwapBuffers();
}
#ifndef ___HEADER_FONT
#define ___HEADER_FONT
#define FONT_DEFAULT_SIZE (100.0f)
void fontBegin();
void fontEnd();
void fontSetPosition(float _x, float _y);
void fontSetSize(float _size);
void fontSetWeight(float _weight);
void fontSetColor(unsigned char _red, unsigned char _green, unsigned char _blue);
void fontDraw(const char *format, ...);
#endif
#include "font.h"
#include <GL/glut.h>
#include <stdio.h>
#include <stdarg.h>
static float positionX;
static float positionY;
static float size = FONT_DEFAULT_SIZE;
static unsigned char color[3];
static float weight = 1.0;
void fontBegin(){
glPushMatrix();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
gluOrtho2D(0, viewport[2], viewport[3], 0);// left right bottom top
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void fontEnd(){
glPopMatrix();
glPopAttrib();
}
void fontSetPosition(float _x, float _y){
positionX = _x;
positionY = _y;
}
void fontSetSize(float _size){
size = _size;
}
void fontSetWeight(float _weight){
weight = _weight;
}
void fontSetColor(unsigned char _red, unsigned char _green, unsigned char _blue){
color[0] = red;
color[1] = green;
color[2] = blue;
}
void fontDraw(const char *format, ...){
va_list argList;
va_start(argList, format);
char str[256];
vsprintf(str, format, argList);
va_end(argList);
glLineWidth(weight);
glColor3ub(color[0], color[1], color[2]);
glPushMatrix();
glTranslatef(positionX, positionY + size, 0.0);
float s = size / FONT_DEFAULT_SIZE;
glScalef(s, -s, s);
for (char *p = str; *p != '\0'; p++)
glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
glPopMatrix();
}
書式付き文字列の作り方なんてどこで使うんだとか思ってたら使う機会が来ました笑。
ついでにMakefileに追記してからコンパイル、実行。
うまく文字列を表示することができました!
#1日目まとめ
- ゲームの骨格ができた
- 描画処理を分離できた
- 文字列描画に必要な機能が多分そろった
完成まで遠いなあ。あと文章書くの難しい。もし最後まで見てくださる方がいたら、改善点など教えていただけたらなと思います。
#参照
ゲヱム道館様の動画を参考にさせていただきました。