スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

ゲームAI同士を対戦させてみる

初めまして。新人?アルバイトのzeosuttです。


秋です。

皆でゲームAIを持ち寄って対戦したい季節ですね。

何のゲームにしましょうか。

そうですね、簡単に実装できるので、〇×ゲームにしましょう。

f:id:spookies_yano:20170919125328p:plain
実は、両者が最善を尽くすと必ず引き分けになる

さて、どんなに強いAIを作っても、それを対戦させる仕組みがなくては意味がありませんね。

この記事では、AI同士を対戦させるための仕組みを作ってみます。

AIの作成自体は主題ではありません。

人対人

まずは、人間同士で対戦ができるようにしましょう。

#include <stdbool.h>
#include <stdio.h>
#include <string.h>

const char *sign = "ox";   // 〇/× は o/x で表現

// 手を表現する構造体
typedef struct {
    int i, j;
} Choice;

// 盤面を初期化する
void init_field(char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        strcpy(field[i], "...");
    }
}

// 指定されたストリームに盤面を送る
void send_field(const char field[3][4], FILE *fp) {
    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%s\n", field[i]);
    }
}

// 指定されたストリームから手を受け取る
void receive_from_ai(Choice *pchoice, FILE *fp) {
    fscanf(fp, "%d %d", &pchoice->i, &pchoice->j);
}

// 手が有効か?
bool is_valid(const Choice *pchoice, const char field[3][4]) {
    const int i = pchoice->i, j = pchoice->j;
    return i >= 0 && i < 3 && j >= 0 && j < 3 && field[i][j] == '.';
}

// 手をシミュレートする
void simulate(int player, const Choice *pchoice, char field[3][4]) {
    field[pchoice->i][pchoice->j] = sign[player];
}

// 指定されたプレイヤーが勝利しているか?
bool is_win(int player, const char field[3][4]) {
    // 手抜きだけど怒らないで
    return field[0][0] == field[0][1] && field[0][1] == field[0][2] && field[0][2] == sign[player]
        || field[1][0] == field[1][1] && field[1][1] == field[1][2] && field[1][2] == sign[player]
        || field[2][0] == field[2][1] && field[2][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][0] && field[1][0] == field[2][0] && field[2][0] == sign[player]
        || field[0][1] == field[1][1] && field[1][1] == field[2][1] && field[2][1] == sign[player]
        || field[0][2] == field[1][2] && field[1][2] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][1] && field[1][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][2] == field[1][1] && field[1][1] == field[2][0] && field[2][0] == sign[player];
}

// 盤面を表示する
void print_field(const char field[3][4]) {
    send_field(field, stdout);
}

// 手を表示する
void print_choice(const Choice *pchoice) {
    printf("choice: %d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char field[3][4];
    int turn;

    init_field(field);

    print_field(field);
    for (turn = 0; turn < 9; turn++) {
        const int player = turn & 1;
        Choice choice;

        printf("AI%d(%c)'s turn\n", player + 1, sign[player]);

        // 一時的に標準入力を指定
        receive_from_ai(&choice, stdin);

        print_choice(&choice);

        if (!is_valid(&choice, field)) {
            const int oppo = player ^ 1;
            puts("invalid input");
            printf("AI%d(%c) win!\n", oppo + 1, sign[oppo]);
            break;
        }

        simulate(player, &choice, field);

        print_field(field);

        if (is_win(player, field)) {
            printf("AI%d(%c) win!\n", player + 1, sign[player]);
            break;
        }
    }
    if (turn == 9) {
        puts("draw...");
    }

    return 0;
}

はい。できました。

座標はi jの形式で入力してください。

例えば、左上なら0 0、その下なら1 0となります。

AI対AI

〇×ゲーム自体のコードは書けました。

後は、AIと通信をするように修正すれば完成です。

ここで、AIには、次の例のようなフォーマットで入力を与えることとします。

o
...
.o.
..x

最初の行は、自分が〇と×のどちらであるかを表します。

2行目以降は盤面の状態を表します。

また、AI作成者の負担を軽減するためにも、AIからは標準入出力でやり取りできるようにしましょう。

AI実装例

先に、このプログラムの入出力仕様に沿って、簡単なAIを実装しますね。

このプログラムの理解の一助となるほか、AI作成未経験者にとっては、入力を受け取り、手を考え、出力するという、AIの基本的な流れの理解にも役立つかもしれません。

#include <stdbool.h>
#include <stdio.h>

typedef struct {
    int i, j;
} Choice;

// 入力を受け取る
bool input(char *pmark, char field[3][4]) {
    if (scanf(" %c", pmark) == EOF) {
        return false;
    }

    for (int i = 0; i < 3; i++) {
        scanf("%s", field[i]);
    }

    return true;
}

// 手を考える
bool think(Choice *pchoice, int mark, const char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (field[i][j] == '.') {
                // 普通は、各手を何らかの基準で評価し、最も評価値が高いものを選ぶ
                // しかし、今回の主題はAI作成ではないため、ここは手を抜く
                *pchoice = (Choice){i, j};
                return true;
            }
        }
    }

    return false;
}

// 手を出力する
void print_choice(const Choice *pchoice) {
    printf("%d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char mark, field[3][4];

    while (input(&mark, field)) {
        Choice choice;

        if (think(&choice, mark, field)) {
            print_choice(&choice);
        } else {
            // 有効な手がない場合の処理(今回は起こり得ない)
            puts("-1 -1");
        }

        fflush(stdout);
    }

    return 0;
}

左上から順に走査し、〇も×も書かれていない座標があれば、それを出力するAIです。

雑魚です。

対戦プログラムの修正

では、〇×ゲームのコードをAIと通信するように修正しましょう。

まず、以下を追加します。

#include <stdlib.h>
#include <unistd.h>

// コマンドライン引数のバリデーション
void validate(int argc, const char * const *argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s AI1 AI2\n", argv[0]);
        exit(EXIT_FAILURE);
    }
}

// AIと通信をする準備をする
void prepare_ai(FILE *fp_to_ai[2], FILE *fp_from_ai[2], const char * const path_to_ai[2]) {
    for (int i = 0; i < 2; i++) {
        int pipefd_to_ai[2], pipefd_from_ai[2];

        pipe(pipefd_to_ai);
        pipe(pipefd_from_ai);

        if (fork() == 0) {
            // 標準入出力を繋ぎ変えた上で、プロセスをAIで置き換える

            close(pipefd_to_ai[1]);
            dup2(pipefd_to_ai[0], STDIN_FILENO);
            close(pipefd_to_ai[0]);

            close(pipefd_from_ai[0]);
            dup2(pipefd_from_ai[1], STDOUT_FILENO);
            close(pipefd_from_ai[1]);

            execlp(path_to_ai[i], path_to_ai[i], (char *)NULL);

            // AIの実行に失敗した場合はエラー
            fprintf(stderr, "%s: no such file\n", path_to_ai[i]);
            exit(EXIT_FAILURE);
        }

        close(pipefd_to_ai[0]);
        close(pipefd_from_ai[1]);

        // AIと高水準入出力関数でやりとりできるようにしておく
        fp_to_ai[i] = fdopen(pipefd_to_ai[1], "w");
        fp_from_ai[i] = fdopen(pipefd_from_ai[0], "r");
    }
}

// 指定されたストリームに、AIへの入力にあたるデータを送る
void send_to_ai(int player, const char field[3][4], FILE *fp) {
    fprintf(fp, "%c\n", sign[player]);
    send_field(field, fp);
    fflush(fp);
}

prepare_ai()がこの記事の肝です。

やっていることは単純で、

  1. pipe()でAIとやり取りするためのパイプを作成し、

  2. 後ほど子プロセスをAIに置き換える、という意図を持ってfork()し、

  3. 子プロセスの標準入出力をdup2()で先ほどのパイプに書き換え、

  4. 最後に子プロセスをexec()でAIに置き換えて

いるだけです。

各システムコール等の詳細はmanを読んでください。

なお、言うまでもなく、Windowsでは動作しません。

最後に、main()を次のように修正します。

65c117,120
< int main(void) {
---
> int main(int argc, char **argv) {
>    validate(argc, (const char **)argv);
> 
>    FILE *fp_to_ai[2], *fp_from_ai[2];
68a124,125
>    prepare_ai(fp_to_ai, fp_from_ai, (const char **)argv + 1);
> 
78,79c135,136
<       // 一時的に標準入力を指定
<       receive_from_ai(&choice, stdin);
---
>        send_to_ai(player, field, fp_to_ai[player]);
>        receive_from_ai(&choice, fp_from_ai[player]);

完成です。

$ ./tic_tac_toe ./ai1 ./ai2 のようにして実行してください。

公平性の問題やセキュリティの問題がありますが、とりあえずこれでおkとしましょう。

お疲れ様でした。

戦略SLGの移動範囲計算を実装してみた

こんにちは、ロックです!

今回は皆さんもお馴染みのゲームジャンル「戦略SLG」についてです!

戦略SLGとは


ターン性でキャラクターを動かし合うシミュレーションゲーム。

代表としては、ファミコンウォーズシリーズですね。

今回の主題を考えると、ファイアーエムブレムシリーズにも密接に関わってくるでしょう。

どちらにも共通してキャラクターを動かす時にどれくらい移動できるかが表示されます。

本日はその移動範囲を求めてみたいと思います。

環境


  • Ubuntu16.04(VirtualBoxでの仮想環境)
  • gcc version 5.4.0

c言語とCUIで表現してみようと思います。

実行結果画面


f:id:ishiyamacocoa:20170727113049p:plain

前準備


  1. map_move
    • 20*20の移動範囲計算結果格納
  2. cal_can_move_range(y, x, n)
    • 移動範囲を計算して map_move を更新していく関数

以上のものを用意します。

それでは考えて行きましょう。

方針


  • 位置:(5,5)
  • 移動可能量:3
  • 検索順:↑→←↓
  • 検索条件①:一度行った場所は検索しない(足跡)
  • 検索条件②:????

1.思考用に9*9用いてみましょう。

1 2 3 4 5 6 7 8 9
1
2
3
4
5 P
6
7
8
9

2.上が歩けそうなので上に1歩行ってみましょう。

1 2 3 4 5 6 7 8 9
1
2
3
4 1
5 P
6
7
8
9

3.まだ上に行けそうです、上に2歩目。

1 2 3 4 5 6 7 8 9
1
2
3 2
4 1
5 P
6
7
8
9

4.まだまだ上に行けそうです、上に3歩目。

1 2 3 4 5 6 7 8 9
1
2 3
3 2
4 1
5 P
6
7
8
9

5.3歩動いてしまいました。これ以上は上に行けないので、一つ前のマスから右へ3歩目を踏み出してみましょう。

1 2 3 4 5 6 7 8 9
1
2 3
3 2 3
4 1
5 P
6
7
8
9

6.3歩目なのでこれ以上動けません、一つ前のマスに戻って左へ行ってみましょう。

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 1
5 P
6
7
8
9

7.このように続けていくと、以下のようになりますね(まだ終わりではない)

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 3 2 1 2 3
5 3 P 3
6 3 2 1 2 3
7 3 2 3
8 3
9

おやおや、左右に伸びきれてませんね。

検索の都合上、(4,5),(6,5)は3歩目の足跡がついてしまうのですね。

これを修正するのはとても簡単です。

以下の検索条件を加えてあげれば良さそうです。

検索条件②:すでに足跡がついていても、現在の歩数よりも大きければ、上書きする

8.すると、こうなります。

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 3 2 1 2 3
5 3 2 1 P 1 2 3
6 3 2 1 2 3
7 3 2 3
8 3
9

無事に求めていたことが実現できました。

では実際のコードはどのように実現できるでしょうか。

設計


  • map_move
    • 20*20の移動範囲計算結果格納

これに歩数を入れていきます。

さて、肝心のコードですが、マスを順々に追っていくような処理に適したものがあります。

それは、再帰です。

再帰でなくとも良いですが、再帰だと短く書けるのでおすすめです。(某パズルゲームの連結しているかも再帰で判定できますね)

実際のコードがこちら

void cal_can_move_range(int y, int x, int n){
  // 足跡をつける(現在歩数を代入)
  map_move[y][x] = n;

  // 残り歩数が0ならば終了
  if(n == 0) return;
 


  // 上へ行けるなら
  if(map_move[y-1][x] < n) cal_can_move_range(y-1,x,n-1);

  // 右へ行けるなら
  if(map_move[y][x+1] < n) cal_can_move_range(y,x+1,n-1);

  // 左へ行けるなら
  if(map_move[y][x-1] < n) cal_can_move_range(y,x-1,n-1);

  // 下へ行けるなら
  if(map_move[y+1][x] < n) cal_can_move_range(y+1,x,n-1);
}

※ map_moveは全て-1で初期化しておく

1 2 3 4 5 6 7 8 9
1
2 0
3 0 1 0
4 0 1 2 1 0
5 0 1 2 P 2 1 0
6 0 1 2 1 0
7 0 1 0
8 0
9

※ 残り歩数を入れているので、方針時とは数字が異なる

これで完成です。

発展編


マスによっては移動コストが異なる場合もあるでしょう。

平地であれば移動に1かかるが、森であれば移動には2必要である。

これは実は簡単です。

先ほど n-1 していた部分を変えてあげれば良いでしょう。

平地なら n-1 森なら n-2 という風に。

そこまで実装した画面が一番初めに見せた実行結果画面ということになります。

f:id:ishiyamacocoa:20170727113049p:plain

^ が森として、移動コストは2必要としています。どうなるか見てみましょう。

f:id:ishiyamacocoa:20170727122326p:plain

森を通ると残り歩数の減少量が2であることがわかるかと思います。

# は壁を意味しており、通行不可マスです。

移動コストは100必要としています(これで実質通行不可)

f:id:ishiyamacocoa:20170727122331p:plain

あとがき


ということで本日の主題は、戦略SLGの移動範囲計算を実装してみた、でした!

今年の冬は自作でこれを用いたゲームでも作ってみたいなーと夢想しているロックがお送りしました!

ありがとうございました!

3Dモデルを作りたい

みなさまこんにちは、新人のnytkと申します。

趣味は、ゲームと、TCG(カードゲーム、とくにマジック・ザ・ギャザリング)です。

以前、社内でカードゲーム勉強会もしました。

ゲームの中で3Dのモデルが動くっていいですよね。

特に、自分が作ったやつが動いたら楽しそうです。

突然ですが、3Dのモデルを作ってみたくなったので作ってみます。

今回使うのは昔少し触ったMetasequoia LEです。(新しいやつ買おう)

起動するとこんな画面が出ます。

f:id:spookies_nytk:20170714094350p:plain

今回作ろうと思うのは画鋲です。 3Dでも簡単なやつならすぐに作れるということを見せようと思います。

今回は回転体の機能を使って作ります。(だから回転で作れそうな画鋲です。)

回転体とは断面をぐるっとまわした軌跡の部分をモデルにするというものです。

というわけで断面を作りましょう。

f:id:spookies_nytk:20170714094610p:plain

座標をぽちぽち入力、数値を直接しないでクリックでもいいです(ずれるのが嫌なので数値で打ってますが)

f:id:spookies_nytk:20170714094948p:plain

こんな感じでしょうか。

断面を作ったらオブジェクトを選択して、

f:id:spookies_nytk:20170714095032p:plain

回転体を適用しましょう

f:id:spookies_nytk:20170714095257p:plain

f:id:spookies_nytk:20170714095405p:plain

(ちょっと分厚いような)

大丈夫です、後からでも修正可能です。

範囲選択を選んで

f:id:spookies_nytk:20170714095454p:plain

この辺りを選択して

f:id:spookies_nytk:20170714095609p:plain

移動を選んで

f:id:spookies_nytk:20170714095636p:plain

選択部分を下にずらします。

f:id:spookies_nytk:20170714095726p:plain

ほっそりしました。

あとは色でもつけましょう。

材質を作成して色を黄色にします。

f:id:spookies_nytk:20170714095848p:plain

材質を未着色部分に貼り付けます

f:id:spookies_nytk:20170714095950p:plain

完成!

f:id:spookies_nytk:20170714100033p:plain

ちょっとしょぼい気もしますが、こんな感じに3Dを作成することができます。

今回使ったMetasequoiaは、新しいものも出ています。

metaseq.net | 3Dモデリングソフトウェア「Metasequoia(メタセコイア)」公式サイト

EX版はUnityで使える.fbxにも対応しているみたいですね。

WebVRはVRの主流になりうるか

こんにちは、モリタです。

VR業界の動きが気になって仕方がないのですが、最近はWebVRに注目しています !

WebVRという言葉をあまり聞いたことがいない人もいるかと思うので軽く説明すると、OculusRiftやHTCViveなどのヘッドセットをwebブラウザ上で動かそうという試みです。
ヘッドセットを扱うためのAPIが提供され、 JavaScriptからヘッドセットを操作することができます。ただ、現状はchromeのwebVR開発用ビルド版とFirefox Nightlyでのみサポートされているようです。
実際にFirefox NightlyでHTCViveを動かしてみましたが、まれにブラウザが落ちたりヘッドセットの動きと表示にタイムラグが発生することがある(ネイティブアプリではラグが発生することはないのでPCのスペックの問題ではなさそう)などまだ開発中という印象はありました。


f:id:moritanian:20170628160324j:plain 写真右がHTCVive, 右がモバイルVR用ヘッドセット

WebVRフレームワーク

mozillaのA-frame や、facebookのReactVR など、WebVR用の様々なフレームワークも登場しています。それらのほとんどで3Dライブラリのthree.jsが利用されており、three.jsはWebVRのビュー部分のスタンダードのようです。

aframe.io

https://facebook.github.io/react-vr/facebook.github.io


モバイルVR

f:id:moritanian:20170628155846j:plain モバイルVRでは、レンズのついたモバイルVR用のヘッドセットにスマホ端末を入れて使います。端末には左右に画面分割し視差を設けた動画を表示するだけでよいので通常のブラウザだけで動かすことができます。
googleはモバイルVRの分野で低価格のcardBoardやVRプラットフォームのdaydream を発表しています。
googleのChromeExperimentsForVertualReality のページではスマホVRに対応したコンテンツを見ることができます。 vr.chromeexperiments.com

モバイルVRを試してみた

スマホVRでは端末に搭載されている加速度センサと磁気センサから姿勢を求めることで頭の動きに合わせて画面をうごかしています。
実際にスマホ用のヘッドセットを被ってみたのですが、うまく頭の動きに合わせて画面が動きません。スマホ用のヘッドセットから端末を外して端末単体を傾けると正しく動いていました。どうやらスマホ用ヘッドセットに金属か磁石がふくまれているからのようです。ダンボール製のcardBoardなどでは問題がおこらないと思いますがみなさんもご注意ください!


モバイルVRガジェット

  • DayDreamVR : DayDreamに対応したモバイルVRヘッドセットです。cardBoardとは違いファブリック素材でできており、従来のものよりも快適な体験ができるとのことです。専用のコントローラが付属します。日本では発売が決まっていない模様。 madeby.google.com

  • ZenFoneAR : こちらは Google Tango(AR(拡張現実)のgoogleプラットフォーム)とGoogle DayDreamの両方に対応した世界初の端末です。日本では今年の夏発売とアナウンスされていたのですが、ついに6月23日に発売開始されたようです。(欲しい!) www.asus.com

WebVR の今後

WebVRのメリットはurlだけでコンテンツを共有でき、アプリのインストールが不要であることが挙げられるでしょう。 開発者側にとってもクロスプラットフォームであることやアプリ審査が不要であることは大きいのではないでしょうか。
ネイティブと比較して速度の問題などまだまだ課題はあると思いますがwebAssemblyやProgressiveWebAppsなど様々な試みもあり今後解決されていくのではと思います。お手軽に体験できるという意味でWebVRはモバイルプラットフォームと相性がいいのではと思っているので今後WebVRが盛り上がっていくことを期待しています!!

擬似3Dダンジョンの作ってみよう!

擬似3Dダンジョンとは


擬似3Dダンジョンと聞いてなんぞやと思う人もいるでしょう。

ずばり、2D上で3Dっぽく見せるダンジョンということになります。

擬似3Dダンジョンを使った作品で有名な物を二つご紹介

  • ウィザードリィ
    • 言わずと知れた名作RPG
    • 1981年にAppleⅡ用に世間へ羽ばたく
    • 魔物の画像を表示したコマンド式戦闘システムを確立、多くのゲームに影響を与えた
  • 世界中の迷宮
    • ウィザードリィを意識して作られた現代版ウィザードリィ
    • 音楽が良い

未プレイの方にはおすすめのゲームです!

自己紹介


さて、本題に入る前に少しだけ自己紹介。

情報系大学の3年生のロックです。

名前の由来は溢れ出るロックンロール精神でしょうか。

気がついたらロックと呼ばれていました(不思議ですね)

そんな私ですが、普段は大学サークルでゲームを作っていたりします。

大学1,2年の間はしっかりと作品を完成させることが出来ずに積もり積もっていくガラクタ生産工場長でした。

しかし、最近少しずつ作品を完成させることが出来るようになってきました。

ガラクタからコードをひっぱてきたりすることもあるので、続けて来たからこそ今があると実感する今日この頃です。

前置き


今回の開発環境は以下の通りです。

  • Visual Studio 2015
  • C/C++
  • DXlib

Windowsでの開発となります。

主な実装機能


では、本題に入りましょう。

擬似3Dダンジョンを作るにあたって、今回実装する機能を紹介しておきます。

  1. ミニマップの表示
    • マップと現在位置とゴールの3種類を表示します
  2. 自由に歩ける
    • キーボードの矢印キーで自由に動けるようにします
  3. 視界の表示
    • 擬似3Dの描画をするための準備
  4. 擬似3Dの描画
    • 肝心要。主題
  5. ゴールを作る
    • 一応のゴール

目標としては擬似3Dの描画が出来れば達成とします。

それでは、一つずつ見ていきましょう。

処理の流れ


1. ミニマップの表示

ミニマップとは言っていますが、がっつりマップですね。

今回は擬似3Dとして最終的に描画してしまうので、全体マップというものが見れないことになってしまいます。

そこでこのミニマップを実装します。

< 実装方法 >

二次元配列を用意して、数字で埋めましょう。

  0:なし 1:壁 2:ゴール 3:初期位置

マップの大きさは12*12ぐらいにしておきましょうか。

最後に、数字に合わせて描画すれば完成(今回は色で表現)

  0:灰色 1:白 2:赤

初期位置に関しては描画しなくて良いでしょう。

代わりにプレイヤーの位置は表示することにします。

  プレイヤー:青

2. 自由に歩ける

プレイヤーも動けた方が良いですよね。

< 実装方法 >

Dxlib にキーボード入力を取得出来る関数があるので、それを用いればばっちりです。

  ↑:前へ進む ←→:旋回 ↓:後ろを向く

今回はマス目移動。

プレイヤーは位置と向きを持つことになります。

3. 視界の表示

突然出てきた視界という単語。

今回は3マス分の視界にしましょう。

左奥 中央奥 右奥
左中 中央中 右中
左前  私  右前

3*3のマスを調べて、壁があるのかないのかを判定出来れば良いです。

「私(プレイヤー)」の場所は調べなくても良いので、8マス調べることになります。

これが上下左右の4方向あることも踏まえて実装を考えます。

< 実装方法 >

プレイヤーの位置とプレイヤーの向きを基準にして、マス目を調べる

調べた結果は視界用の3*3の二次元配列に格納(プレイヤーの位置には 0 とか入れておきましょ)

視界用配列から、視界を取得して扱います。

せっかくなので、ミニマップと同様の手順で、視界も表示しておくことにします。

4. 擬似3Dの描画

ようやく来ました。

と言っても、ここまで順番にやってきたら、処理としてはすることがありません。

視界用配列を値を元にして、ぺたぺた画面に画像なり図形なりを貼り付けていく作業になります。

画像を用意するのは大変なので、図形を用います。

< 実装方法 >

Dxlib に四角形を描画する関数があるので、それを用いる。

左奥、中央奥、右奥、左中、中央中、右中、左前、右前、の8つそれぞれに四角形を描画する。

5. ゴールを作る

マップを作ったときに実はゴールが出来ています。

あとはそれを取得するだけです。

< 実装方法 >

プレイヤー位置のマップ情報を取得して、ゴールならゴールする

成果物


f:id:ishiyamacocoa:20170622235041p:plain 右上:ミニマップ   ミニマップの下:視界   左:擬似3D描画

f:id:ishiyamacocoa:20170623011253p:plain ゴールしました。

おまけ


触れていませんでしたが、通路も描画しています。

中央、中央奥にかけて、真っ暗じゃない床を描画しておくと、方向感覚が狂いにくいです(ないとわかりづらい)

これでウィザードリィも作れるぞーと思っていますが、特に作る予定はない。

知的好奇心だけで実装しちゃうお茶目なロックでした。

ありがとうございました。

以下に拙いですが、ソースコードを載せておきます。

  • Dungeon3D.cpp
  • Dungeon3D.h

の二つです。

改めて、ありがとうございました。

Dungeon3D.cpp


gist08766e3ec20e70c56dd91de28944df00