読者です 読者をやめる 読者になる 読者になる

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

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

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

書き手:肥田野

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

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

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

イベントの実装までにかなりの時間を要してしまいましたが、今回でいよいよプレイヤーキャラが任意でイベントを起動できるようになります。

さっそくPlayerクラスにイベント起動用の関数を実装したいのですが、その前にRPGによく使われるイベントについて考えてみましょう。

今回のようなメッセージは基本中の基本ですが、他にも大事な要素がありますね。「村人」です。

誰もいない世界を冒険するのは寂しいですし(そういうゲームもありますが)、ゲームを進めるうえでの情報源も立札だけでは無機的で面白くありませんね。

今回は村人の実装は行いませんが、その準備だけはしておきましょう。

具体的には、「Playerクラス」を「Charaクラス→Playerクラス」の親子構造にするのです。

キャラクターの動きはCharaクラスで定義して、今回のイベントを起動させる等プレイヤーキャラにしか使わない要素はPlayerクラスにまとめます。

そうすれば、あとでCharaクラスからMurabitoクラスを派生させられますね。

というわけでPlayer.hから見ていきましょう。

≪Player.h≫

#pragma once
#include<DxLib.h>

class Map;
class EventMap;

class Chara{

public:
    int gh[12];
    int width,height;
    int walkVec;
    int animCount;
    bool walkFlag;
    int x,y;//プレイヤーが実際に存在する座標
    int targetX,targetY;//プレイヤーが次に向かうべき座標
    int speed;
    
    Chara(char* add);

    void AnimationView(int animState,int firstNum);

    void View();
    
    void Move(Map* map[],int walkSwitch);

    void All();
};

class Player:public Chara{
public:
    Player(char* add);

    void Action(EventMap* eMap);

    void All(Map* map[],EventMap* eMap);
};

Playerクラスに新たにAction関数を作った他、CharaクラスにView関数を移植したり、Move関数及びAll関数を少し改造しました。

≪Player.cpp≫

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

Chara::Chara(char* add){
    LoadDivGraph(add,12,3,4,20,28,gh);
    GetGraphSize(gh[0],&width,&height);
    walkVec = 2;
    walkFlag = false;
    animCount = 0;
    speed = 2;
}

void Chara::AnimationView(int animState,int firstNum){
    if(animState == 0)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[firstNum],TRUE);
    if(animState == 1)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[firstNum+1],TRUE);
    if(animState == 2)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[firstNum+2],TRUE);
    if(animState == 3)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[firstNum+1],TRUE);
}

void Chara::View(){

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

    if(animState == 4){//animStateが4になったらリセット
        animCount = 0;
        animState = 0;
    }

    if(walkFlag){
        if(walkVec == 2)AnimationView(animState,0);
        if(walkVec == 4)AnimationView(animState,3);
        if(walkVec == 6)AnimationView(animState,6);
        if(walkVec == 8)AnimationView(animState,9);
        animCount++;
    }else{
        if(walkVec == 2)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[1],TRUE);
        if(walkVec == 4)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[4],TRUE);
        if(walkVec == 6)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[7],TRUE);
        if(walkVec == 8)CameraDraw(x,y-2*(height-width),x+CELL_WIDTH,y+CELL_WIDTH,gh[10],TRUE);
    }
}

void Chara::Move(Map* map[],int walkSwitch){
    walkFlag = false;//基本はfalse、歩いている時だけ変更する

    if(targetX == x && targetY == y){//移動中でなければ
        if(walkSwitch == 2){
            walkVec = walkSwitch;
            walkFlag = true;
            if(map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT+1].canwalk&&
                map[1]->cell[x/CELL_WIDTH][y/CELL_HEIGHT+1].canwalk&&
                map[2]->cell[x/CELL_WIDTH][y/CELL_HEIGHT+1].canwalk){
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT+1].canwalk = false;//次に進マスのcanwalkをfalseに
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT].canwalk = true;//元いたマスのcanwalkはtrueに
                    targetY+=CELL_HEIGHT;//targetYを1マス分移動
            }
        }else if(walkSwitch == 4){
            walkVec = walkSwitch;
            walkFlag = true;
            if(map[0]->cell[x/CELL_WIDTH-1][y/CELL_HEIGHT].canwalk&&
                map[1]->cell[x/CELL_WIDTH-1][y/CELL_HEIGHT].canwalk&&
                map[2]->cell[x/CELL_WIDTH-1][y/CELL_HEIGHT].canwalk){
                    map[0]->cell[x/CELL_WIDTH-1][y/CELL_HEIGHT].canwalk = false;
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT].canwalk = true;
                    targetX-=CELL_WIDTH;
            }
        }else if(walkSwitch == 6){
            walkVec = walkSwitch;
            walkFlag = true;
            if(map[0]->cell[x/CELL_WIDTH+1][y/CELL_HEIGHT].canwalk&&
                map[1]->cell[x/CELL_WIDTH+1][y/CELL_HEIGHT].canwalk&&
                map[2]->cell[x/CELL_WIDTH+1][y/CELL_HEIGHT].canwalk){
                    map[0]->cell[x/CELL_WIDTH+1][y/CELL_HEIGHT].canwalk = false;
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT].canwalk = true;
                    targetX+=CELL_WIDTH;
            }
        }else if(walkSwitch == 8){
            walkVec = walkSwitch;
            walkFlag = true;
            if(map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT-1].canwalk&&
                map[1]->cell[x/CELL_WIDTH][y/CELL_HEIGHT-1].canwalk&&
                map[2]->cell[x/CELL_WIDTH][y/CELL_HEIGHT-1].canwalk){
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT-1].canwalk = false;
                    map[0]->cell[x/CELL_WIDTH][y/CELL_HEIGHT].canwalk = true;
                    targetY-=CELL_HEIGHT;
            }
        }else{
            animCount = 0;//キーが押されておらず、移動が完了していればanimCountをリセット
        }
    }else{
        walkFlag = true;
    }
    if(y < targetY)y+=speed;
    if(x > targetX)x-=speed;
    if(x < targetX)x+=speed;
    if(y > targetY)y-=speed;
}

void Chara::All(){
    View();
}

Player::Player(char* add):Chara(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();
            }
        }
    }
}

void Player::All(Map* map[],EventMap* eMap){
    int walkSwitch = 0;
    if(CheckHitKey(KEY_INPUT_DOWN ))walkSwitch = 2;
    if(CheckHitKey(KEY_INPUT_UP   ))walkSwitch = 8;
    if(CheckHitKey(KEY_INPUT_RIGHT))walkSwitch = 6;
    if(CheckHitKey(KEY_INPUT_LEFT ))walkSwitch = 4;
    
    Action(eMap);
    Move(map,walkSwitch);
    View();
}

void Chara::Move(Map* map[],int walkSwitch){

以前はCheckHitKeyから直接動かしていましたが、今後村人などを設置していくにあたって、より汎用的に扱えるよう引数を増やしました。

プレイヤーキャラのキー入力はPlayer::Allで行っています。ご確認ください。

void Player::Action(EventMap* eMap){

引数はイベントマップのポインタです。

for文の条件に使われている「eMap.size()」は、ベクタeMapの要素の数を返します。

プレイヤーの向きに応じてイベントが存在するかを調べて、存在すればそのActivate関数を実行します。

プレイヤーとイベントの準備が整ったので、Controlクラスを少し書き換えて仕上げになります。

≪Control.h≫

#pragma once

class Player;
class Map;
class EventMap;

class Control{
public:
    Player* pl;
    Map* map[3];
    EventMap* ev;
    int camX,camY;

    Control();

    ~Control();

    void All();
};

EventMapクラスをプロトタイプ宣言して、メンバ変数にイベントマップのポインタを用意しました。

≪Control.cpp≫

#include"Control.h"
#include"Player.h"
#include"define.h"
#include<DxLib.h>
#include"Map.h"

Control::Control(){
    camX = camY = 0;
    pl = new Player("graphic\\Player.png");
    map[0] = new Map("csv\\flame1.csv");
    map[1] = new Map("csv\\flame2.csv");
    map[2] = new Map("csv\\flame3.csv");
    ev = new EventMap("csv\\event.csv");
    pl->x = pl->targetX = CELL_WIDTH;
    pl->y = pl->targetY = CELL_HEIGHT;
}

Control::~Control(){
    delete pl;
    for(int i=0;i<3;i++)delete map[i];
    delete ev;
}

void Control::All(){
    
    if(pl->x>WINDOW_X/2 && pl->x<map[0]->width-WINDOW_X/2+CELL_WIDTH){
        viewX = pl->x-WINDOW_X/2;
    }
    if(pl->y>WINDOW_Y/2 && pl->y<map[0]->height-WINDOW_Y/2){
        viewY = pl->y-WINDOW_Y/2;
    }

    for(int i=0;i<3;i++)map[i]->BackView();
    pl->All(map);
    for(int i=0;i<3;i++)map[i]->FrontView();
    ev->All();
    pl->Action(ev);
}

Control::Allにて、EventオブジェクトのAll関数を実行するのと、プレイヤークラスのAction関数を実行させています。

ActionってPlayer::Allに入れてもいいんじゃないかという気がしたのですが、なぜかキーが反応しなくなるので別にしました。原因は調査中です。たぶんPushKeyあたりに問題があるのかも……(-_-;)

さて、これで実行してみてください。

きちんとcsvファイルとtxtファイルを編集していれば、指定した座標に向かってスペースキーを押すことでメッセージが表示されたかと思います。

いやはや、イベント一つ作るのにずいぶん時間がかかってしまいましたね。

今後は新たなイベントやその拡張をすることで、よりゲームらしくしていきたいと思います。