#これまでのあらすじ
- ゲームの骨格を作った
- ウィンドウに文字がだせるようになった
1日目はここです。
#2日目
弾幕シューティングづくり2日目です。今日やることを書く前に一応。n日目って書き方してますが連日投稿は厳しいです笑。n回目にしたほうがよかったかなと思っています。まあ、できる限り連日投稿目指して頑張ります。
今回やることはゲームの骨格を改良します。前回の骨格だとゲームの「シーン」を管理するのに都合が悪いです。ゲームのシーンとは、メニューとか難易度選択とかシューティングするところとか...みたいな1つの場面を表すことにします。つまり、前回作った骨格だとシーンからシーンへの移り変わりがうまくできないんです。というわけで、今回やるのはシーン管理を意識したゲームの骨格を目指します。
#今日の目標
- シーン管理が(比較的)簡単なゲーム骨格
#ゲームのシーン管理
前回、ゲームは大雑把に入力、計算、描画の連続でできているって書きました。しかし、それはシーンごとで別々の処理になるはずです。つまり、メニューシーンの描画処理とシューティングゲームシーンの描画処理の内容は全く違うものになるはずで、少なくともシーンごとに計算処理と描画処理があることになります。ここでC言語とglut縛りが急に大きくのしかかってきます笑。といってもこの縛りでやり通すつもりなので、なんとかします。(クラス使えないのつらい)
まずはシーンの遷移を設計します。シーンはスタックを使って管理します。このようにすると、行き来ができるシーンがあったときに役立ちます。ウィンドウに映して実際に動いているのはスタックの一番上のシーンにします。
| | | | | | | | | | | |
| | | | | s3 | | | | s3 | | |
| | -> | s2 | ->| s2 | -> | s2 | -> | s2 | -> | |
| s1 | | s1 | | s1 | | s1 | | s1 | | s4 |
+----+ +----+ +----+ +----+ +----+ +----+
上の図でs1はメニューシーン、s2は難易度選択シーン、s3は装備選択シーン、s4はシューティングゲームシーンとします。まずメニューから難易度選択シーンに移りさらに装備選択シーンに行ったとします。このとき難易度を選びなおしたいとき、難易度選択シーンの情報を保持してあったほうがゲームっぽいです。そして、シューティングゲームシーンに移るともうそれまでのシーンの情報は残しておかなくてもいいので消せます。このような管理ができるため、スタックでの管理を選びました。
次にシーンが持つべきデータを考えます。上でも書きましたが、計算(更新)処理と描画処理を持ちます。さらに、シーン独自で保持し、次のシーンに渡せるパラメータが必要になります。
シーンの管理、つまりシーンのスタックを管理するのはgame.cにします。
とりあえずここまでの内容を実装してみます。
#シーン管理部分の実装
シーンの情報はscene_tという構造体で表します。シーンのスタックはscene_tのスタックとして、sceneStack_tという構造体で表します。それを踏まえて、前回作ったgame.cにいろいろ手を加えていきます。
#include "game.h"
#include <GL/glut.h>
#include "define.h"
#include "scene.h"
static sceneStack_t stk;
static scene_t allScene[SCENE_MAX] = {
};
void glInit(int *argc, char **argv);
void gameLoop();
void gameInit(int *argc, char **argv){
glInit(argc, argv);
sceneStackInit(&stk);
unsigned char p[3] = {};
sceneStackPush(
&stk,
allScene[SCENE_MENU].m_update,
allScene[SCENE_MENU].m_display,
allScene[SCENE_MENU].m_parameter);
glutDisplayFunc(allScene[SCENE_MENU].m_display);
}
void gameRun(){
// test message
glutMainLoop();
}
void glInit(int *argc, char **argv){
//initialization
glutInit(argc, argv);
glutInitDisplayMode(GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutCreateWindow("myGame");
glutIdleFunc(gameLoop);
}
void changeScene(enum eScene s, unsigned char *p, int clear){
}
void gameLoop(){
sceneStackTop(&stk)->m_update(changeScene);
}
まず、gameLoopという関数をglutIdleFuncに渡します。そのあと、gameInit関数の中で、シーンスタックを初期化します。ここでは、メニューシーンというシーンがあるものとして、一番初めに実行されるシーンに設定します。そして、実際にシーンを動かすのはgameLoop関数の中です。シーンスタックの一番上にあるシーンの更新関数を呼び出します。changeScene関数は各シーンに渡されるコールバック関数です。この関数を各シーンの更新関数内で呼び出すと、シーンが移り変わるようにします。
次にこれらを実現するシーンの実態を作っていきます。
#ifndef ___HEADER_SCENE
#define ___HEADER_SCENE
enum eScene {
SCENE_MENU,
SCENE_GAME,
SCENE_MAX
};
#define SCENE_PARAMETER_MAX 3
#define SCENE_STACK_MAX 8
typedef void (*updateFunc)(void (*changeSceneFunc)(enum eScene, unsigned char *, int));
typedef void (*displayFunc)(void);
typedef struct scene_ {
updateFunc m_update;
displayFunc m_display;
unsigned char m_parameter[SCENE_PARAMETER_MAX];
} scene_t;
typedef struct sceneStack_ {
scene_t m_scene[SCENE_STACK_MAX];
int m_top;
} sceneStack_t;
void sceneStackPush(sceneStack_t *stk, updateFunc update, displayFunc display, unsigned char *p);
void sceneStackPop(sceneStack_t *stk);
void sceneStackInit(sceneStack_t *stk);
scene_t *sceneStackTop(sceneStack_t *stk);
#endif
各シーンの更新関数updateと描画関数display、シーンのパラメータを持つシーン構造体scene_tとそのスタックであるsceneStack_tを定義し、スタック操作関数を用意しました。パラメータの部分は、具体的なものを作り始めるときにまた考えます。あとは実装するだけ。
#include "scene.h"
#include <GL/glut.h>
void sceneStackPush(sceneStack_t *stk, updateFunc update, displayFunc display, unsigned char *p){
if (stk->m_top < (SCENE_STACK_MAX - 1)) {
stk->m_top++;
int t = stk->m_top;
stk->m_scene[t].m_update = update;
stk->m_scene[t].m_display = display;
for (int i = 0; i < SCENE_PARAMETER_MAX; i++)
stk->m_scene[t].m_parameter[i] = p[i];
} else {
//スタックプッシュエラー
}
}
void sceneStackPop(sceneStack_t *stk){
if (stk->m_top > 0) {
stk->m_top--;
} else {
//スタックポップエラー
}
}
void sceneStackInit(sceneStack_t *stk){
stk->m_top = 0;
}
scene_t *sceneStackTop(sceneStack_t *stk){
return &(stk->m_scene[stk->m_top]);
}
スタックに関しては詰めればいいんです精神でやっつけ適当です。いつかなんとかします。
これでなんとかシーン管理ができるようになりました。次に簡単なメニューシーンとゲームシーンを作ります。
#メニューシーン
ウィンドウにmenuという文字列を表示するだけのシーンをメニューシーンとして作成します。
#ifndef ___HEADER_SCENEMENU
#define ___HEADER_SCENEMENU
#include "scene.h"
void sceneMenuUpdate(void (*changeSceneFunc)(enum eScene, unsigned char *, int));
void sceneMenuDispaly();
#endif
#include "sceneMenu.h"
#include <GL/glut.h>
#include "font.h"
static int count = 0;
static unsigned char p[SCENE_PARAMETER_MAX] = {1,2,3};
void sceneMenuUpdate(void (*changeSceneFunc)(enum eScene, unsigned char *, int)){
//update
count++;
if (count == 500){
count = 0;
changeSceneFunc(SCENE_GAME, p, 1);
}
glutPostRedisplay();
}
void sceneMenuDispaly(){
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("meun");
fontEnd();
glutSwapBuffers();
}
とりあえずパラメータとカウンタを用意して、カウンタが500になるとt次のゲームシーンに移るようにします。
#ゲームシーン
ウィンドウにgameという文字列とメニューシーンから渡されるパラメータを表示するだけのシーンにします。
#ifndef ___HEADER_SCENEGAME
#define ___HEADER_SCENEGAME
#include "scene.h"
void sceneGameUpdate(void (*changeSceneFunc)(enum eScene, unsigned char *, int));
void sceneGameDispaly();
void sceneGameInit(unsigned char *p);
#endif
#include "sceneGame.h"
#include <GL/glut.h>
#include "font.h"
static int count = 0;
static unsigned char param[SCENE_PARAMETER_MAX];
void sceneGameInit(unsigned char *p){
for (int i = 0; i < SCENE_PARAMETER_MAX; i++)
param[i] = p[i];
}
void sceneGameUpdate(void (*changeSceneFunc)(enum eScene, unsigned char *, int)){
//update
count++;
if (count == 500){
count = 0;
changeSceneFunc(SCENE_MENU, param, 1);
}
glutPostRedisplay();
}
void sceneGameDispaly(){
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("game(%d, %d, %d)", param[0], param[1], param[2]);
fontEnd();
glutSwapBuffers();
}
パラメータを受け取ってシーンを初期化する関数を用意しました。あとはメニューシーンと大体同じです。
次に、これらのシーンをgame.cに登録して実際にコンパイル、実行してみます。
#シーンの登録
game.cに作った各シーンの処理と、シーン変更時に呼ばれる処理を追加します。
//以下をファイルに追記
#include "sceneMenu.h"
#include "sceneGame.h"
static scene_t allScene[SCENE_MAX] = {
{sceneMenuUpdate, sceneMenuDispaly, 0}, //sceneMenu
{sceneGameUpdate, sceneGameDispaly, 0} //sceneGame
};
void changeScene(enum eScene s, unsigned char *p, int clear){
if (clear)
sceneStackInit(&stk);
sceneStackPush(
&stk,
allScene[s].m_update,
allScene[s].m_display,
p);
glutDisplayFunc(allScene[s].m_display);
switch (s) {
case SCENE_MENU:
break;
case SCENE_GAME:
sceneGameInit(p);
break;
default:
// error
break;
}
}
このような感じで、各シーンを用意した後、各シーンの処理を登録し、シーン変更時に呼ばれる関数に追記するだけでシーンを管理できるようになりました。個人的にはかなり頑張ったつもりですが、またいろいろ変更するかもしれません...。
#実行
適当にMakefileを書き換えてコンパイル、ビルドして実行すると、適当なタイミングでシーンが入れ替わると思います。
#2日目まとめ
- シーン管理ができるゲームの骨格ができた
この先もなんですけど、この記事書くのは目標のプログラムが出来上がった後なんですよね笑。今回の内容は個人的に本当に大変だった。クラスなしで設計するのがなかなかしんどかったし、実際は管理しやすくはなったどポンコツな部分がそこそこあると思うので、もし見てくださって「あまりにもここはひどい」といった感想を持たれた方はぜひアドバイス頂けたらと思います。よろしくお願いします。
なんとか完成までもっていくぞ!