ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

【ゲーム製作入門】C/C++で簡単なRPGを作る②【DXライブラリ】

書き手:肥田野

DXライブラリでRPGのベースになるマップ画面やデータ管理などのあれこれの制作に挑戦してみます。

RPGとして完成する保証はありませんが、途中経過だけでも参考になれば幸いです。

この連載は前回までの内容を理解していることを前提に進めていきます。

第二回は、前回ロードしたプレイヤーのチップ画像でアニメーションをさせる方法について解説します。

今回説明するコードです。

《Player.h》

#include<DxLib.h>

#define WALKTIME 15
#define ANIMATION_VIEW(firstNum) if(animState == 0)DrawExtendGraph(x,y,x+width*2,y+height*2,gh[firstNum],TRUE);\
               if(animState == 1)DrawExtendGraph(x,y,x+width*2,y+height*2,gh[firstNum+1],TRUE);\
               if(animState == 2)DrawExtendGraph(x,y,x+width*2,y+height*2,gh[firstNum+2],TRUE);\
               if(animState == 3)DrawExtendGraph(x,y,x+width*2,y+height*2,gh[firstNum+1],TRUE);
class Player{
public:
    int gh[12];
    int width,height;
    int walkVec;//歩く方向。2,4,6,8でテンキーに対応
    int animCount;//マイフレーム1ずつ増やす、アニメーションのためのカウンタ
    bool walkFlag;//歩いているか立ち止まっているかの判定
    int x,y;//グラフィックを描画する座標
    Player(){
        LoadDivGraph("Chara.png",12,3,4,20,28,gh);
        GetGraphSize(gh[0],&width,&height);
        walkVec = 2;
        walkFlag = false;
        animCount = 0;
        x = 0;
        y = 0;
    }

    void Move(){
        walkFlag = false;//基本はfalse、歩いている時だけ変更する
        
        if(CheckHitKey(KEY_INPUT_DOWN)){
            walkFlag = true;
            walkVec = 2;
        }else if(CheckHitKey(KEY_INPUT_LEFT)){
            walkFlag = true;
            walkVec = 4;
        }else if(CheckHitKey(KEY_INPUT_RIGHT)){
            walkFlag = true;
            walkVec = 6;
        }else if(CheckHitKey(KEY_INPUT_UP)){
            walkFlag = true;
            walkVec = 8;
        }else{
            animCount = 0;//キーが押されておらず、移動が完了していればanimCountをリセット
        }
    }

    void View(){

        int animState = animCount/WALKTIME;//animCountがWALKTIMEの倍数になるたびにanimStateが1増える

        if(animState == 4){//animStateが4になったらリセット
            animCount = 0;
            animState = 0;
        }
        
        if(walkFlag){
            if(walkVec == 2){
                ANIMATION_VIEW(0);
            }
            if(walkVec == 4){
                ANIMATION_VIEW(3);
            }
            if(walkVec == 6){
                ANIMATION_VIEW(6);
            }
            if(walkVec == 8){
                ANIMATION_VIEW(9);
            }
            animCount++;
        }else{
            if(walkVec == 2){
                DrawExtendGraph(x,y,x+width*2,y+height*2,gh[1],TRUE);
            }
            if(walkVec == 4){
                DrawExtendGraph(x,y,x+width*2,y+height*2,gh[4],TRUE);
            }
            if(walkVec == 6){
                DrawExtendGraph(x,y,x+width*2,y+height*2,gh[7],TRUE);
            }
            if(walkVec == 8){
                DrawExtendGraph(x,y,x+width*2,y+height*2,gh[10],TRUE);
            }
        }
    }

    void All(){
        Move();
        View();
    }
};

今回変更するのはPlayer.hのみです。

#define WALKTIME 15

WALKTIMEという定数を用意しました。

これは一歩足を出すまでのフレーム数になります。ゲーム自体が60FPSなので、一秒間に四歩進むことになりますね。

#define ANIMATION_VIEW(firstNum) if(animState == ……

なんだか長大なマクロですね。実は、マクロは複数行にまたがって定義することもできるんです。

その場合は、行末に「\」を入れてやる必要があります。

さらに、マクロ名に続けて()を書くと、関数の引数のようにその場に応じて異なる値を設定することができます。

firstNumが定義の中にも使われているのが分かりますね。

この四本のif文がどのような役割を果たすのかは後述します。今はマクロの新しい使い方として覚えておいてください。

int walkVec;

コメント文でも書きましたが、walkVecはプレイヤーが向いている方向をテンキーの数字配置で記憶させます。

別に1,2,3,4とふっても良いのですが、この方が直感的かなという格ゲー脳です(笑)

void Move(){

キーが押されているかどうかで、プレイヤーが歩いているのか否か、どの方向を向いているのかを制御します。

この関数でプレイヤーの状態が制御されて、View関数で描画をさせることにします。

どのキーも押されていない時、つまりif文の最後のelseですが、animCountに0を代入し続けることでアニメーションをリセットしています。

特に意味がないように思われますが、実行した時に、同じ方向へキーを連打してみると違いがわかるかと思います。

elseを入れないと、アニメーションの状態が記憶されてしまうためちょっと違和感を覚えますよね。まあここまで細かいこと気にしないよって人も多そうですが……

int animState = animCount/WALKTIME;

View関数です。 ここでローカル変数として宣言したanimStateに、animCountをWALKTIMEで割った値を代入しています。

たった1行ですがこれが非常に大事な要素です。RPGに限らずモノを動かすプログラムではよく使われる方法(だと思う)ので必ず理解しておきましょう。

animCountはクラスの変数なので、値は保持されます。また、毎フレームごとにインクリメント(+1)されていきます。

WALKTIMEは最初に15と定義しましたね。int型の変数は少数を持つことができないので、整数を整数で割った結果が少数になったとき、無理やり整数に直される性質があります。

この時切り捨てなのか切り上げなのかはその時の規格によってコロコロ変わるのですが、とりあえず切り捨てられると仮定しましょう。

animCountは0から始まり1ずつ増えていきます。14に到達するまではanimCount/WAKETIMEは1より小さい値を取るので、animStateに保存される数は0になります。

animCountが15以上30未満の時は1が、31以上45未満なら2が保存されます。

こうすることで、15フレームに1回数値が上がっていく変数が出来上がります。後はこの数にgh配列の添字(gh[i]←このiのこと)を対応させれば、animCountが増え続ける限りghが切り替わり続けるのです。

ただし、animStateが4に達したら0に戻すようif文で修正を入れています。

ふと、違和感を感じたかもしれませんね。同じ方向を向いている画像は3枚しかないのに、animStateは0、1、2、3の4つの値を持つことになります。

これは実際にアニメーションを動かしてみないとピンと来ないのですが、例えば上の3枚、手まえを向いて歩いている時の画像はどういう順番で切り替えれば良いのでしょう。

0→1→2→0→1→2でいいのでしょうか。答えはノーです。0→1→2→1→0→1→2→1と、一歩足を動かすごとに真ん中の画像を差し込んでやらないと不自然なんです。

それを把握した上で次に進んでください。

ANIMATION_VIEW(0);

いよいよ冒頭の長いdefineを紐解く時なのですが、先にDXライブラリのDrawExtendGraph関数について理解しておきましょう。

DrawExtendGraphはDrawGraphの拡張版で、画像のサイズを変更することができます。引数は左から順に、左上X座標、左上Y座標、右下X座標、右下Y座標、グラフィックハンドル、透過の可否です。

それを踏まえた上でマクロ定義に戻ってみてください。

animStateが0~3の4パターンで場合分けしています。DrawExtendGraphで画像を2倍に拡大しています。オリジナルのサイズだと、ちょっと小さく感じましたので。

プレイヤーの向いている方向によって表示したい画像のグラフィックハンドルが異なるので、一番左の数値(0,3,6,9)を基準に+1、+2、+1としています。

あとはView関数に戻って確認すると、歩いている方向によって0,3,6,9のいずれかの数値を振り分けているのがわかります。

なお、このアニメーションをさせるのはwalkFlagがtrueのときだけで、falseなら直立している画像のみを表示させています。

最後にAll関数に新しく作ったMove関数を登録してやれば、無事にプレイヤーが歩き出すと思います。

……その場で足踏みしてるだけですけどね。

今回はここまでです。次回は、いよいよプレイヤーの座標を動かして歩かせてみます。

この連載では直接取り扱いませんが、ウディタ自体もRPGを作るイメージをねるにはちょうどいいツールなので、時間があったら是非遊んでみることをおすすめしますよ。