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

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

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

書き手:肥田野

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

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

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

第11回は前中後の三部に加えて、不具合修正の11.5回で構成されています。特に11.5回を飛ばしていないかよく確認してから、今回の記事に進んでくださいね。

さて今回は、いよいよ村人を実装します。

⑪‐後でPlayerクラスをCharaクラスの派生クラスと位置づけたので、新たにつくるMurabitoクラスもCharaクラスから派生させます。

つまりPlayerクラスとMurabitoクラスは、画像の読み込み(Charaクラスコンストラクタ)や歩かせる処理(Chara::Move())でCharaクラスの関数を共有していて、イベントを調べる処理(Player::Action())や話しかけられた時の処理(Murabito::Reset()※これから作ります)は別々に保有していることになります。

このように、一部だけ違う処理であとは共通の処理をさせたい時は、クラスの継承を使うことでコードを短縮することができるんです。

なお、のちのちパーティメンバー(プレイヤーの後ろに着いてくる人たち)を作りたいと思った時にも、このCharaクラスが便利に使えそうですね。

それではコードを見ていきましょう。まずはPlayer.hから。

《Player.h》

#pragma once
#include<DxLib.h>

class Map;
class EventMap;
class MessageWindow;

class Chara{
    //変更なし
};

class Player:public Chara{
    //変更なし
};

class Murabito:public Chara{
public:
    MessageWindow* msg;
    int walkCount;

    Murabito(int defX,int defY,char* ghAdd,char* msgAdd);

    ~Murabito();

    void Reset(int playerVec);

    void All(Map* map[]);
};

話しかけたら何か喋ってもらわないと困るので、MessageWindowクラスのポインタを一つ持たせます。

walkCountは、ランダムに歩き出すタイミング(ここでは2秒に1回)を計測するために必要になります。

ではPlayer.cppです。

《Player.cpp》

#include"Player.h"
#include<DxLib.h>
#include"Function.h"
#include"define.h"
#include"Map.h"
#include"WindowBox.h"
#include"Event.h"
#include"WindowBox.h"

//Charaクラスは変更がないので割愛

Player::Player(char* add):Chara(1,1,add){

}

void Player::Action(EventMap* eMap){
    if(PushKey(KEY_INPUT_SPACE)){
        for(int i=0;i<eMap->ev.size();i++){
            if(walkVec == 2 && eMap->ev[i]->x*CELL_WIDTH == x && eMap->ev[i]->y*CELL_HEIGHT == y+CELL_HEIGHT||
                walkVec == 4 && eMap->ev[i]->x*CELL_WIDTH == x-CELL_WIDTH && eMap->ev[i]->y*CELL_HEIGHT == y||
                walkVec == 6 && eMap->ev[i]->x*CELL_WIDTH == x+CELL_WIDTH && eMap->ev[i]->y*CELL_HEIGHT == y||
                walkVec == 8 && eMap->ev[i]->x*CELL_WIDTH == x && eMap->ev[i]->y*CELL_HEIGHT == y-CELL_HEIGHT){
                eMap->ev[i]->Activate(walkVec);
            }
        }
    }
}

void Player::View(Map* map[]){
    //変更なし
}

void Player::All(Map* map[],EventMap* eMap){
    //変更なし
}


Murabito::Murabito(int setX,int setY,char* ghAdd,char* msgAdd):Chara(setX,setY,ghAdd){
    msg = new MessageWindow(msgAdd);
    walkCount = 0;
}

Murabito::~Murabito(){
    delete msg;
}

void Murabito::Reset(int playerVec){
    if(!msg->live){
        msg->Reset();
    }
    if(playerVec == 2)walkVec = 8;
    if(playerVec == 4)walkVec = 6;
    if(playerVec == 6)walkVec = 4;
    if(playerVec == 8)walkVec = 2;
}

void Murabito::All(Map* map[]){
    walkCount++;
    int walkSwitch = 0;//歩き出す時の方向を決める。歩かないときは0
    if(walkCount%120 == 0){
        walkCount = 0;
        walkSwitch = GetRand(4)*2;//方向は2,4,6,8のどれか
    }
    if(msg->live){
        msg->All();
    }else{
        Move(map,walkSwitch);
    }
    View();
}

Player::Actionも若干変更していますが、まずはMurabitoクラスから。

Murabitoクラスに実態を持たせるには、X,Y座標とキャラグラフィック画像、そして喋らせる内容が書かれたテキストファイルの場所が必要です。

グラフィック画像とテキストは、こんなものを用意しました。

f:id:NUT_SoftwareDevelopper:20150621171015p:plain

ミクさん風に。画像の中身は問題ではないので、自分で好きに作った方がいいです。

《村人A.txt》

ようこそ、私たちの村へ!;
え、私しかいない?;
……い、今に賑やかになるんですからっ!

テキストファイルの場所はMessageWindowクラスのコンストラクタの引数にそのまま投入します。

残りの3つはCharaクラスのコンストラクタの引数にそのまま使われているのがわかりますね。

Reset関数は基本的にMessageWindowオブジェクトのReset関数を呼び出すだけですが、そこに加えて「話しかけられたらプレイヤーの方を向く」処理もさせています。

なので引数にplayerVecを用意して、その値によってMurabito自身の方向を変更しています。

そこでPlayer::Action関数を見てください。

Activate関数を呼び出すときに引数が追加されていますね。

というわけで、次はActivate関数を持つEventクラスです。

《Event.h》

#pragma once

class MessageWindow;
class Murabito;
class Map;

class Event{
public:
    int x,y;
    MessageWindow* msg;
    Murabito* mbt;

    Event(int setX,int setY,char* add);

    void Activate(int walkVec);

    void All(Map* map[]);
};

Murabitoクラスのポインタ「mbt」を用意しました。

一つのクラスに複数ポインタを持たせることになりますが、実際に使うのはどれか一つです。

Murabitoクラスを実体化させれば、そこにMessageWindowクラスのポインタが含まれていますからね。

Activate関数に方向が加わった以外は大きな変化はありません。

では詳細を見てみます。

《Event.cpp》

#include"Event.h"
#include"WindowBox.h"
#include"Player.h"
#include"Map.h"
#include<DxLib.h>

Event::Event(int setX,int setY,char* add){
    x = setX;
    y = setY;
    msg = NULL;
    mbt = NULL;
    char buf[255];
    int addp = 0;
    memset(buf,0,sizeof(buf));
    while(1){
        bool breakFlag = false;
        if(add[addp] != ','){//イベントタイプのセルを読みこむ
            buf[addp] = add[addp];
            addp++;
        }else{
            int bufp = 0;
            addp++;//セルの間の「,」を読み飛ばす
            if(strcmp(buf,"メッセージ") == 0){
                memset(buf,0,sizeof(buf));
                while(1){
                    if(add[addp] != '\n' && add[addp] != EOF){
                        buf[bufp] = add[addp];
                        bufp++;
                        addp++;
                    }else{
                        msg = new MessageWindow(buf);
                        breakFlag = true;
                        break;
                    }
                }
            }else if(strcmp(buf,"村人") == 0){
                memset(buf,0,sizeof(buf));
                char txt[255];//二つ以上の文章を同時に読むには別々の入れ物が必要
                memset(txt,0,sizeof(buf));
                while(1){
                    if(add[addp] != ','){
                        buf[bufp] = add[addp];
                        bufp++;
                        addp++;
                    }else{
                        addp++;
                        bufp = 0;
                        break;
                    }
                }
                while(1){
                    if(add[addp] != '\n' && add[addp] != EOF){
                        txt[bufp] = add[addp];
                        bufp++;
                        addp++;
                    }else{
                        mbt = new Murabito(setX,setY,buf,txt);
                        breakFlag = true;
                        break;
                    }
                }
            }else{
                DebugBreak();
            }
            if(breakFlag)break;
        }
    }
}

void Event::Activate(int walkVec){
    if(msg != NULL)msg->Reset();
    else if(mbt != NULL)mbt->Reset(walkVec);
}

void Event::All(Map* map[]){
    if(msg != NULL)msg->All();
    else if(mbt != NULL){
        mbt->All(map);
        x = mbt->targetX/CELL_WIDTH;
        y = mbt->targetY/CELL_HEIGHT;
    }
}

}else if(strcmp(buf,"村人") == 0){

書き加えたのはここからです。

今後、イベントを追加するときはここのif文の条件を書き加えることで増やしていきます。

Murabitoクラスは画像ファイルのアドレスとテキストファイルのアドレスの両方が必要なので、新たにchar型の配列txt[255]を追加してあげます。

文字数を数えるためのbufpとaddpは使いまわせるので、そのまま使っています。

Activate関数では、追加した引数walkVecを、MurabitoクラスのReset関数の引数に指定しています。

All関数ではelse ifでMurabitoクラスのAll関数を実行させていますが、Murabitoはあちこち歩き回るため、その度にイベントオブジェクトの座標も動かさないといけません。

x = mbt->targetX/CELL_WIDTH;はそのための処理になります。

最後に、EventMapクラスに読み込ませるcsvファイルも書き加えましょう。

f:id:NUT_SoftwareDevelopper:20150621171357j:plain

これで村人が実装できたかと思います。

立札のような説明文と、今回作ったキャラクターだけでも、ノベル形式のゲームが作れてしまいそうですね。

しかしあくまで目指すのはRPGなので、次回もよりそれっぽくなるような機能を実装させてみましょう。