三択クイズを改良してテキスト型のロールプレイングゲームを作って見ましょう。
ゲームとして成り立たせるには、物語と、それに合った選択、そして、選択の結果によって起きるイベントなど必要です。
また、PRGの場合、キャラクターのステータス値も必要になります。
テキスト型のゲームでは、先に作ったクイズ形式の他、ロールプレイングゲームや、アドベンチャーゲームがあります。
ロールプレイングゲームの場合、自分のキャラクターが敵と戦いながら目的を果たしたり、キャラクターを目的に応じて育成することで目標を達成させます。
戦闘型の場合、体力値や攻撃値、防御値などを使って敵と戦わせます。徐々に強い敵と戦い勝利を収めることが目的となります。
育成型の場合、体力、知力、敏捷性、耐久性などステータスの変化によって、育成結果の違いを楽しみます。目的の育成ができるかどうかは運次第です。
アドベンチャーゲームの場合、RPG的な要素を混ぜながら探索を行ったり、必要なアイテムを集めながら順次目標を達成して進行します。
行動選択型の場合、キャラクターの行動を選択することで順次探索を続けて行きます。物語が中心となり流れを分岐しながら進みます。
アイテム収集型の場合、1つのエリア内を移動しながら必要なアイテムを揃えることが目標です。1つの目標を達成するとまた次のエリアへ進み、これを繰り返します。
物語の進行は、きまった流れで行うのか、それとも選択によって流れが変わるのか。それによってシステムの根幹が変わります。
現時点では順番に出力する仕組みですから、1つの流れを追いかける物語にすると改造はほとんど必要ないでしょう。
流れを変更するには、選択肢毎に次の進行先を明示する必要があります。また、同じところをグルグル回れるようにするのか、分岐して他のルートに進めないようにするなど、流れ方も様々です。
どのようなステータスを用意するかでゲーム性に大きく関わります。
例えば、HP(ヒットポイント、体力値)だけで管理するならば、それが0になるとゲームオーバーとなります。ゲーム内の選択によってHPを増減させることでゲーム性を出します。
また、HPだけと言っても、どのタイミングで増減させるのかは非常に重要なポイントとなります。
ステータスの種類が増えると、それがゲーム内で何を意味し、何に影響を与えるか考えなければなりません。
例えば、HPと攻撃力、防御力と3つのステータスを用意した場合、攻撃力が敵の防御力で減殺され敵のHPを奪うというダメージ計算をし、また逆に敵からのダメージも計算してHPを変動させます。その計算方法や、敵のステータスの必要になってきます。ゲーム内のシステムは複雑になりますが、その分、駆け引きの要素が増えるのです。
また、ゲーム内で得られるアイテム(拾得物)をどう扱うかによって、ゲームの進行に大きく関わります。内部的にはステータスのように管理することになります。
まずは、最も簡単な方法でゲームを作ってみましょう。
シナリオは順序にそって進行します。
ステータスはHPのみで、選択によって増減する。HPの初期値は100で、上限は200、0になったらゲームオーバーとします。
土台となるのは三択クイズで作った「quiz.html」です。これをコピーして「text_rpg.html」として保存します。
まずは、表示部分の文言を変更します。
基本構造は変えないようにします。
<head>
<meta charset="utf-8">
<title>テキストRPG</title>
</head>
<body>
<h1>テキストRPG</h1>
<hr>
<h2>ストーリー</h2>
<div id="text_q"></div>
<h2>選択</h2>
<div id="text_s"></div>
<h2>行動結果</h2>
<div id="text_a"></div>
ストーリー部には物語のテキストと、キャラクターのステータスも同時に表示することにします。
選択部はそのままです。
行動結果部には、行動選択の結果が表示されます。
クイズの問題セットの部分には、物語と行動選択、選択の結果を用意します。
//ストーリー qa = new Array(); qa[0] = ["海辺を散歩していると子供達が騒いでいる。<br>数人が棒を持って砂浜を叩いていた。","声を掛ける","子供達を観察する","何もしない",10,0,-10]; qa[1] = ["子供達は大きな海亀を棒で突いたり、砂を掛けたりして騒いでいる。","大きな声で注意する","優しく声を掛ける","一緒に騒ぐ",0,10,-10]; qa[2] = ["子供達は私を残して走り去った。","亀の上に乗る","亀をひっくり返す","亀を水で洗う",-10,-5,10]; qa[3] = ["海亀は泣きながらお礼を言っているようだ。","亀の上に乗る","亀をひっくり返す","亀の顔を洗う",-5,-10,10]; qa[4] = ["気がつくと亀の背中に乗っていた。<br>そしてなぜか海の中にいた。","亀の上にしっかりと座る","亀をひっくり返す","顔を洗う",10,-10,5]; qa[5] = ["亀はどんどん海の底へと潜っていく。<br>しかし、暗くなるどころか、前方に光が見える。","光を見つめる","亀をひっくり返す","服を洗う",5,-5,10]; qa[6] = ["そこは竜宮城と書かれた海底の宮殿であった。<br>わたしは亀の背中に乗ったままその中に案内された。","亀から降りる","亀の上に立つ","亀の背中で寝る",5,10,-5]; qa[7] = ["竜宮城の広間に乙姫と名乗る女性がいた。<br>彼女は私が亀を助けたと思っているらしい。","「私が助けました」","「私は何もしていません」","「早く玉手箱をよこせ」",20,5,-10]; qa[8] = ["乙姫様が玉手箱をくれた。けして開けてはならないという。","持って帰る","いらない","ここで開ける",10,0,-50]; qa[9] = ["気がつくと私は砂浜に倒れていた。そして手元には玉手箱があった。","開ける","開けない","開ける",-20,20,-10]; //初期設定 q_sel = 3; //選択肢の数 setReady();
ここでの改良はアンケート調査「enquete.html」と同じ仕組みに変えていきます。
3つの選択肢の後、3つの選択結果のポイントがあります。これがHPを増減させます。
ユーザー関数setReadyにHPの初期設定を追加します。
//初期設定
function setReady() {
count = 0; //ストーリー番号
my_hp = 100; //HP
//最初のストーリー
quiz();
}
配列変数ansersを削除し、変数my_hpを追加します。
ストーリーを管理する配列変数qaの仕様に合わせて、ユーザー関数quizの中も作り替えます。
//ストーリーと選択肢の表示
function quiz() {
var s, n;
//ストーリー
document.getElementById("text_q").innerHTML = "第" + (count + 1) + "話:HP" + my_hp + "<br>" + qa[count][0];
//選択肢
s = "";
for (n=1;n<=q_sel;n++) {
if (qa[count][n] != "") {
s += "【<a href='javascript:anser(" + n + ")'>" + n + ":" + qa[count][n] + "</a>】";
}
}
document.getElementById("text_s").innerHTML = s;
}
ストーリーの表示の中にHPの表示も追加します。
選択肢は特に変更はありませんが、三択クイズの「選択肢の数が変動する場合」の改良を施した状態になっています。
ユーザー関数anserを修正します。
//行動選択結果
function anser(num) {
var s;
s = "第" + (count + 1) + "話<br>";
//HPの増減
my_hp += qa[count][num+q_sel]; //結果表示 s += "HP"; if (qa[count][num+q_sel] >= 0) s += "+"; s += qa[count][num+q_sel];
document.getElementById("text_a").innerHTML = s;
変数my_hpには選択結果を加算してHPを増減させています。
次に結果表示を作ります。「HP+10」とか「HP-10」という形で結果を表示させます。「+」は数字に含まれていないため、文字として追加しています。
まだ、終了時の処理内に使わない配列変数ansersが残っているため、10話の後でエラーが発生します。
ユーザー関数anserの後半部分を修正します。
//次の問題を表示
count++;
if (count < qa.length) {
quiz();
} else {
//終了
if (my_hp > 150) {
s = "玉手箱を開けてしまった。モクモクと煙が立ち上がる。<br>";
s += "そして、煙が消えたとき目の前に乙姫様が現れた。<br>";
s += "二人は末永く幸せにくらしましたとさ・・・";
} else if (my_hp > 100) {
s = "玉手箱を開けることはできなかった。<br>";
s += "わたしは家に帰り、二度とその箱を開けることはなかった。<br>";
s += "もう一度乙姫様に会いたいなぁ・・・";
} else if (my_hp > 50) {
s = "玉手箱を開けてしまった。モクモクと煙が立ち上がる。<br>";
s += "そして、煙が消えたとき目の前に海亀が現れた。<br>";
s += "もう竜宮城へは戻れないようだ。かわいそうに・・・";
} else {
s = "玉手箱を開けてしまった。モクモクと煙が立ち上がる。<br>";
s += "そして、煙が消えたとき私は老人になっていた。<br>";
s += "おしまい・・・";
}
document.getElementById("text_q").innerHTML = s;
//次の選択肢
s = "【<a href='javascript:history.back()'>前のページに戻る</a>】";
s += "【<a href='javascript:setReady()'>同じ物語を最初から</a>】";
s += "【<a href=''>次の物語に進む</a>】";
document.getElementById("text_s").innerHTML = s;
}
テーブルを削除して、終了時に最後のストーリーを表示します。
最初に、HPは上限200まで、0以下でゲームオーバーと決めましたので、そのチェックを行う処理を作ります。
変数my_hpが変動した直後でチェックをします。ユーザー関数anserの中です。
//HPの増減
my_hp += qa[count][num+q_sel];
//結果表示
s += "HP";
if (qa[count][num+q_sel] >= 0) s += "+";
s += qa[count][num+q_sel];
document.getElementById("text_a").innerHTML = s;
//HPのチェック
if (my_hp > 200) my_hp = 200; //上限を超えさせない
if (my_hp <= 0) {
game_over(); //ゲームオーバー
} else {
//次の問題を表示
count++;
if (count < qa.length) {
quiz();
} else {
//終了
(省略)
document.getElementById("text_q").innerHTML = s;
//次の選択肢
s = "【<a href='javascript:history.back()'>前のページに戻る</a>】";
s += "【<a href='javascript:setReady()'>同じ物語を最初から</a>】";
s += "【<a href=''>次の物語に進む</a>】";
document.getElementById("text_s").innerHTML = s;
}
}
}
変数my_hpが0以下になるとユーザー関数game_overに飛びます。その後、処理がまたここに戻ってきてしまうため、それ以降の処理はelseを使って処理を分岐させています。
elseに対応した最後の「}」も忘れないようにしましょう。
次にユーザー関数game_overを新しく作ります。
function game_over() {
var s;
s = "ゲームオーバー<br>";
s += "あなたは気を失って浜辺に倒れていた。起き上がってみたものの名前も住んでいた場所さえも思い出せない。<br>";
s += "これは夢ではないだろうか。<br>";
s += "そう思い。もう一度眠ることにした。";
document.getElementById("text_q").innerHTML = s;
//次の選択肢
s = "【<a href='javascript:history.back()'>前のページに戻る</a>】";
s += "【<a href='javascript:setReady()'>同じ物語を最初から</a>】";
document.getElementById("text_s").innerHTML = s;
}
ゲームオーバーのときは、前のページに戻るか、最初からやり直すかの二択になっています。
これでテキストRPGは完成です。