Skip to content

DBで作るHTML5時代のネットゲームの作り方/どきどき☆ダンジョンの作り方

やあshi3zだ。

ついにDB機能がついてゲームデータを保存できるようになったenchant.jsと9leap。ちなみにDB機能はenchant.jsを経由しなくても9leapにAPIが用意されているからそれを直接使えばだれでも使うことが出来るよ(まだ技術ページに書いてないみたいだけど)。

これを使ってどんなゲームが作れるか、さっそく僕も作ってみたんだけど、「どきどき☆ダンジョン」はもう遊んでくれたかな?

今回はこのゲームの作り方の解説だよ!
これを読めばキミもネットゲームが簡単に作れるかも!?

さてさて、この「どきどき☆ダンジョン」はめちゃくちゃシンプルなダンジョン系RPGを目指して作られている。

画面はめちゃくちゃ地味なんだけど、面白さは損なわないようにしているつもり。
ま、これに絵を付けるのはそれほど難しくないんだけどさ。絵を描く方が大変だもんね。

んで、見てわかるようにこのゲームはいつものように「アトラスX」を使ってる。

手前味噌だけど、アトラスXはゲームの本質的な問題を考える上ではとても便利なんだ。
なにしろ日本語だしね。

とはいえ、今回のゲームはおそらくアトラスXを使ったものとしてもかなり複雑な部類に入るだろうね。

最初のプロローグのシーンは、アトラスXとしてはいつもの感じで作られている。

プロローグ = function(){
    if( !フラグ.所持金 ){
        フラグ.所持金=0;
        フラグ.死亡階=0;
    }
    if(フラグ.所持金<0)フラグ.所持金=0;
    フラグ.フロア=0;
    フラグ.獲得ゴールド=0;
    フラグ.HP=10;
    台詞("どきどきダンジョン");
    一時停止();
    台詞("あなたは名もなき冒険者です");
    一時停止();
    台詞("これから危険がいっぱいの洞窟に潜ります");
    一時停止();
    台詞("出来るだけ多くの財宝を持ち帰ってください");
    一時停止();
    台詞("ではお気をつけて");
   
    選択肢("ダンジョンへ",フロア);
    選択肢("お店へ",お店);
};

ここでif( !フラグ.所持金)としているところに注意。
この「!」がついていると、「逆」という意味になるんだ。

つまり、この場合は「フラグ.所持金」という項目が存在しなかったら、続く{〜}を実行すること、という意味になる。

んで、フラグ.所持金の間に小さな「.(半角ピリオド)」が入っていることもにも注意。
この「フラグ」の後ろに半角ピリオドを打った後の単語は、9leapのDBに保存される部分になるんだ。

保存したい変数は「フラグ.」を頭に付けて、保存しなくてもいい変数は付けない、というルールを覚えておこう。

フラグ.所持金が存在しない場合、それはつまりこのゲームを初めて遊ぶ場合なんだ。
そのときにまず所持金を0に、死亡階を0にリセットしているんだよ。

そのif文を抜けた後でもさらにフラグ変数の初期化をしていて、フラグ.フロアはダンジョンの地下何階に居るのかを示していて、フラグ.獲得ゴールドは、ダンジョンの中で拾った財宝の金額を示している。

フラグ.HPはいわずもがなのHPだ。

これらはゲームを開始するときに必ずリセットしなければならない変数だね。

最後の選択肢で、お店に行くかダンジョンに行くか選択肢を表示しているというわけ。
お店は後回しにして、まずはダンジョンへ行った場合の処理を見てみよう。

フロア = function(){
    フラグ.フロア++;
    台詞("レベル:"+フラグ.フロア);

       //ここらへん省略

    var 運命 = 乱数(10);
    if(フラグ.フロア>20)運命++;
    if(フラグ.フロア>30)運命++;
    switch(運命){
        case 9:
            台詞("ん・・・?");
            一時停止();
            台詞("あそこになにかあるぞ!");
            一時停止();
            道具ID = 乱数(アイテム.length/2)+乱数(フラグ.フロア/10);
            if(アイテム.length<=道具ID)
                道具ID=アイテム.length-1;
            道具 = アイテム[道具ID];
            台詞(道具.名前+"が落ちている!");
            一時停止();
            台詞("どうする?");
            if(フラグ.装備){
                台詞("現在の装備:"+フラグ.装備.名前+" (最大攻撃力:"+(フラグ.装備.強さ*フラグ.装備.倍率)+")");
            }
            選択肢("ひろう",function(){
                台詞("君は"+道具.名前+"を拾った!");
                フラグ.装備 = 道具;
                台詞("現在の装備:"+フラグ.装備.名前+" (最大攻撃力:"+(フラグ.装備.強さ*フラグ.装備.倍率)+")");
                セーブ("フロア");
                次へ(フロア);
            });
            選択肢("先へ進む",フロア);
            return;
            break;
        case 0:
        case 1:
        case 2:
        case 3:
        case 6:
        case 7:
        台詞(どれか([
                "不気味な静けさがあたりを支配している",
                "まるで闇に飲み込まれそうだ",
                "この先に一体何が待ち受けているのだろう",
                "ひたりひたりと一歩歩くごとに音がする",
                "ダンジョンはまだまだ続いているようだ",
                "ここで引き返すべきか・・・それとも行くべきか",
                "洞窟がどこまでも続いていく",
                "まるで奈落の底へ続いているかのようだ",
                "不安で背筋がゾクッとなった。どうするか",
                "この辺りで命を落とした冒険者もおおいと聞く",
                "まだ・・・行けるか?",
                "洞窟は薄明かりに照らされている。",
                "いったいどこまで続いているのだろう",
                "果てしなく果てしなく洞窟は奥へ奥へとつづいている",
                "そろそろ怖くなってきた"]
                    ));
            break;
        case 4:
            台詞("おっと・・・");
            一時停止();
            if(乱数(10)==1){
                台詞("誰かが落としたらしい回復薬を見つけた!");
                一時停止();
                台詞("ありがたく頂戴しよう");
                フラグ.回復薬 ++;
            }else{
                台詞("これはありがたい。薬草が生えている!");
                一時停止();
                台詞("あなたは薬草を使い、体力を1回復した");
                フラグ.HP += 1;
            }
            break;
        case 5:
            台詞("ん、なにか光るものがあるぞ・・・");
            一時停止();
            台詞("こ、これは・・・!!");
            宝物 = (乱数(10)+1)*10;
            フラグ.獲得ゴールド += 宝物;
            一時停止();
            台詞("やった! "+宝物+"Gの宝物を発見した!");
            break;
        default:
            台詞("ん・・・こ、この気配は・・・");
            一時停止();
            台詞("ま、まずい!モンスターだ!!");
            フラグ.モンスター = 乱数(10)+乱数(フラグ.フロア/5);
            if(魔物.length<=フラグ.モンスター)
                フラグ.モンスター=魔物.length-1;
            次へ(バトル);
            セーブ("バトル");
            return;
            break;
    }
   
    台詞("残りHP:"+フラグ.HP+" 宝物:"+フラグ.獲得ゴールド+"G");
    if(フラグ.装備){
            台詞("現在の装備:"+フラグ.装備.名前+" (最大攻撃力:"+(フラグ.装備.強さ*フラグ.装備.倍率)+")");
    }

       //ここらへん省略

    選択肢("引き返す",ゲーム終了);
    選択肢("先へ進む",フロア);


    セーブ("フロア");
};

実際のフロアのシーンはもうちょっと複雑なんだけど、ここでは少し省略してみた。
これを見てわかる通り、最初に運命という変数に乱数で0から10までの数字を入れている。
ただし、フロアが20以上、30以上でそれぞれ運命という変数に1を加えている。

この運命というのは、要するに毎回ランダムで決まる訳だ。
ランダムで決まった運命で、たとえば「道ばたで薬草を拾った!」とか「モンスターに遭遇した!」ということが決まるワケ。

このswitch〜case文はJavaScriptの文法をそのまま使っている。
switch()で指定された変数がcaseで示す値だったら、そこにジャンプする、という文法だ。

まあ見ればなんとなく予想はつくよね?

最後の「セーブ(フロア)」という部分にも注目だ。
セーブという命令は今回のアトラスX1.4で初めて登場した命令になる。

これを呼び出すと、フラグ.のついたすべての変数をデータベースにセーブし、なおかつ次にゲームを遊んだときに呼び出される最初のシーンを指定している。ただし、シーンの名前は文字列で渡さなければならないので”"で囲まれていることに注意だ。

こうすることで、所持金が減ったりプレイヤーがダメージを受けるなどの不利な状態変化が起きても大丈夫なようにするわけだね。

このゲームでは時々、友達が落とした財宝を拾ったり、友達の祈りでHPが回復したりするんだけど、その処理は前半の省略した部分にある。

そこだけ抜き出してみると以下のようになっている。

    for(i in game.memories.friends){
        p = game.memories.friends[i];
        if(乱数(10)<1)
        if(p.data){
        if(p.data.死亡階 == フラグ.フロア){
            台詞("むむ?・・・・");
            一時停止();
            台詞("なんと!<font color=#00ff00>"+p.name+"</font>が落とした財宝を見つけた!");
            台詞(" ");
            一時停止();
            if(p.data.死亡金額>1000)p.data.死亡金額=1000;
            フラグ.獲得ゴールド += Math.floor(p.data.死亡金額/2)+5;
            台詞(Math.floor(p.data.死亡金額/2)+"Gを手に入れた!");
            次へ(フロア);
            セーブ("フロア");
            return;
        }
        if(p.data.到達階 == フラグ.フロア){
            台詞("むむ?・・・・");
            一時停止();
            台詞("どうやらなんと!<font color=#00ff00>"+p.name+"</font>がここまで来たようだ");
            台詞(" ");
            一時停止();
            台詞(p.name+"の祈りでHPが5回復!");
            フラグ.HP += 5;
            次へ(フロア);
            セーブ("フロア");
            return;
        }}
    }

コレもまあ若干、複雑に見えてしまうかもしれない。
for( i in game.memories.friends){ 〜} というのは、日本語文法でもなんでもない、JavaScriptの文法をそのまま使っている。

game.memories.friendsはenchant.jsが用意している配列で、ここにほかのプレイヤーの遊んだデータが格納されている。

それを「p = game.memories.friends[i]」とすることで、pに友達だれか一人分のデータが格納されるというわけ。

で、p.dataというのは、ほかのプレイヤーの「フラグ」と同じだと思っていい。
だからp.data.所持金とやるとその友達の所持金がわかる。
p.nameには友達のTwitterでの名前が格納されている。

そこに注意して読み解けば、なんとかわかるはずだ。

実際にモンスターとバトルするシーンでは意外と難しいことはやってないのでさらっと掲載するだけにする

魔物 = [
                {名前:"ネズミ",強さ:1},
                //中略
                {名前:"クリスタルドラゴン",強さ:90},
            ];
               

バトル = function(){
    台詞("なんと!"+魔物[フラグ.モンスター].名前+"が現れた");
    一時停止();
    台詞("やつの強さは"+魔物[フラグ.モンスター].強さ+"だ!どうする!?");
    台詞("残りHP:"+フラグ.HP+" 宝物:"+フラグ.獲得ゴールド+"G");
    if(フラグ.装備){
            台詞("現在の装備:"+フラグ.装備.名前+" (最大攻撃力:"+(フラグ.装備.強さ*フラグ.装備.倍率)+")");
    }
    選択肢("逃げる",function(){
            台詞("君は"+魔物[フラグ.モンスター].名前+"に恐怖を感じ、");
            台詞("せっかく拾った宝物を半分捨てて逃げだした!");
            フラグ.獲得ゴールド = Math.floor(フラグ.獲得ゴールド/2);
            セーブ("バトル");
            一時停止();
            if( 乱数(魔物[フラグ.モンスター].強さ*5) > 10){
                台詞("しかし"+魔物[フラグ.モンスター].名前+"はすばやく回り込んだ");
                ダメージ = 乱数(魔物[フラグ.モンスター].強さ)*2+1;
                台詞(ダメージ+"のダメージを受けた!");
                一時停止();
                フラグ.HP -= ダメージ;
                if(フラグ.HP<=0){
                    台詞("あなたは気を失った");
                    セーブ("プロローグ");
                    次へ(バッドエンド);
                }else{
                    台詞("残りHP:"+フラグ.HP+" 宝物:"+フラグ.獲得ゴールド+"G");
                    次へ(バトル);
                }
            }else{
                台詞("・・・・なんとか逃げられた!");
                台詞("残りHP:"+フラグ.HP+" 宝物:"+フラグ.獲得ゴールド+"G");
                次へ(フロア);
            }
        });
    選択肢("戦う",function(){
            台詞("君は勇敢にも"+魔物[フラグ.モンスター].名前+"に立ち向かった!");
            一時停止();
            ダメージ = 乱数(魔物[フラグ.モンスター].強さ*5)+1;
            if(フラグ.装備){
                台詞(魔物[フラグ.モンスター].名前+"の攻撃! :"+ダメージ);
                一時停止();
                if(乱数(7)==0){
                    台詞("まずい!手が滑った!!");
                    こっちの攻撃力 = 1;
                    ダメージ = ダメージ*3;
                    一時停止();
                    if(フラグ.装備.落としにくさ){
                        if(乱数(フラグ.装備.落としにくさ)==0){
                            台詞("なんと!"+フラグ.装備.名前+"を落としてしまった!!!");
                            フラグ.装備 = false;
                        }
                    }else{
                        if(乱数(3)==0){
                            台詞("なんと!"+フラグ.装備.名前+"が壊れてしまった!!!");
                            フラグ.装備 = false;
                        }
                    }
                }else
                if(乱数(6)==0){
                    台詞("来た!会心の一撃!");
                    こっちの攻撃力 = Math.floor(フラグ.装備.強さ*フラグ.装備.倍率*1.5);
                    一時停止();
                }else{
                    こっちの攻撃力 = (乱数(フラグ.装備.強さ)+1)*フラグ.装備.倍率;
                }
                台詞("あなたの攻撃!:"+こっちの攻撃力);
                ダメージ = ダメージ - こっちの攻撃力;
                if(ダメージ<0)ダメージ=0;
            }
            フラグ.HP -= ダメージ;
            if(フラグ.HP<=0){
                台詞("激闘の末、あなたは気を失った");
                セーブ("プロローグ");
                次へ(バッドエンド);
            }else{
                if(ダメージ>0)
                    台詞("君は"+ダメージ+"のダメージを受けながらも、からくも生き残った");
                else
                    台詞("君は"+魔物[フラグ.モンスター].名前+"を倒した!");
                一時停止();
                台詞("残りHP:"+フラグ.HP);
                セーブ("フロア");
                次へ(フロア);
            }
        });
}

ポイントは「魔物」という配列に魔物のデータをすべて入れていることと、「魔物」配列に同じ魔物のデータをわざとコピーして入れていること。
そうすることで、単純にモンスターの出現確率をいじることが出来る。たくさん出したいモンスターはたくさんコピーして入れておけばいいわけだ。

また、一応、下に行くほど強いモンスターを出すようになっている。

最後はお店だ。

商品リスト = [
            {名前:"ショートソード",説明:"使い勝手の良い短剣",強さ:5,倍率:3,値段:300,落としにくさ:15},
            {名前:"ショートスピア",説明:"当たれば確実にダメージを与える槍",強さ:2,倍率:5,値段:1000,落としにくさ:15},
            {名前:"ロングソード",説明:"長いが取り回し易い長剣",強さ:8,倍率:3,値段:1200,落としにくさ:20},
            {名前:"ロングスピア",説明:"当たれば確実にダメージを与える長槍",強さ:2,倍率:10,値段:1500,落としにくさ:20},
            {名前:"ブロードソード",説明:"使い勝手の良い短剣",強さ:10,倍率:3,値段:5000,落としにくさ:25}
        ];

お店 = function(){
    台詞("所持金:"+フラグ.所持金+"G");
    if(フラグ.装備){
            台詞("現在の装備:"+フラグ.装備.名前+" (最大攻撃力:"+(フラグ.装備.強さ*フラグ.装備.倍率)+")");
    }
    台詞("何を買う?");
    list=[];
    選択肢("もどる",プロローグ);
    for(i in 商品リスト){
        item = 商品リスト[i];
        選択肢(item.名前+" "+item.値段+"G",買おうかな,"欲しい="+i);
    }

    選択肢("回復薬 50G",function(){
            if(フラグ.回復薬){
                if(フラグ.回復薬>=3){
                    台詞("これ以上回復薬は持てません");
                    次へ(お店);
                    return;
                }
            }else
                フラグ.回復薬=0;
            if(フラグ.所持金>50){
                台詞("回復薬を手に入れた!");
                フラグ.回復薬++;
            }else{
                台詞("お金が足りません");
            }
            セーブ(お店);
            次へ(お店);
        });

};

買おうかな = function(){
    item = 商品リスト[欲しい];
    台詞(item.名前+" "+item.値段+"Gを買います");
    台詞("よろしいですか?");
    台詞("所持金:"+フラグ.所持金+"G");
    選択肢("はい",function(){
                item = 商品リスト[欲しい];
                if(フラグ.所持金>item.値段){
                    台詞(item.名前+" "+item.値段+"Gを買いました");
                    フラグ.所持金 -= item.値段;
                    台詞("所持金:"+フラグ.所持金+"G");
                    フラグ.装備 = item;
                    次へ(プロローグ);
                    セーブ(プロローグ);
                }else{
                    台詞("お金が足りません");
                    次へ(お店);
                }
    });
    選択肢("いいえ",お店);
};

買おうとしたら、「買おうかな」というシーンに飛んで「よろしいですか?」というダイアログを出している。

商品リストの中身を変えれば、自分だけのオリジナル商品を陳列することもできる。

このゲームのソースコード全体は、このページの一番下のほうのリンクからダウンロードできる。

わりと長い方だとは思うけど、たったこれだけでRPGっぽいドキドキ感のあるゲームが作れてしまうんだ。

今回、クラシックなRPGには定番のSTRやDEX、INTといった複雑なパラメータは敢えてナシにした。

そういうものに頼らなくても、面白いゲームが作れるということを示したかったからだ。

ああいうパラメータっていうのは、複数人数で同時にプレイするゲームのときに向いてるんだよね。

このゲームをたたき台に改造して、たとえば絵や音を付けたり、ダンジョンを梅田地下街に変えたりしてオリジナルに作ったゲームももちろん9leapに投稿OKだから、ぜひ面白いオリジナルネットRPGを作って僕をアッと言わせてほしい。

じゃあまたね!

このエントリーをはてなブックマークに追加
はてなブックマーク - DBで作るHTML5時代のネットゲームの作り方/どきどき☆ダンジョンの作り方
Post to Google Buzz
Share on GREE

Related posts:

  1. 超革命的スクリプト言語 アトラスXの使い方 #1

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*