基本メニュー表示と結果取得

アドベンチャーゲームを作るという以上、ユーザに入力を促し、その結果によってシナリオを分岐させる機能は必須です。このとき、モーダル(modal)であること*1がより望ましいです。これを実現するのに最も簡単なのはJavaScriptのconfirm、およびpromptを利用することです。(参考:できるだけ単純にJavaScriptでアドベンチャーゲームを作る。 - Backstage of theater.js) しかし、デザイン・機能的に全く融通が利かないのがツライところです。*2

そこで、PrototypeWindowClassのように半透明の要素でブラウザウィンドウを覆い、その上にメニューを表示するようにしてみました。PrototypeWindowClassをそのまま使うこともできるのですが、高機能すぎて重い(^^;ので、自作しました。

これを使用して、Theaterクラスに以下のメソッドを作成しました。

alertメソッド

【使用例】
new Theater({
  scenario: [
    function(){
      this.alert("あらーと1");
    },
    function(){
      this.alert("あらーと2");
    }
  ]
});

シナリオ(関数配列)内の関数で「this.alert(表示文字列);」として使用します。指定文字列とOKボタンが表示されます。OKボタンを押すと閉じ、自動的に次の関数を実行します。なので、上記は「あらーと1」「あらーと2」が続けて表示されます。

confirmメソッド

【使用例】
new Theater({
  scenario: [
    function(){
      this.confirm("かくにん");
    },
    function(result){
      this.alert("けっか:" + result);
    }
  ]
});

シナリオ(関数配列)内の関数で「this.confirm(表示文字列);」として使用します。指定文字列と「はい」と「いいえ」のボタンが表示されます。「はい」か「いいえ」のいずれかのボタンを押すと、閉じて次の関数を実行します。このとき、関数の引数に結果が設定されます。「はい」が押された場合はtrue、「いいえ」が押された場合はfalseです。上記例では表示しているだけですが、これによりシナリオを分岐させることもできます。

promptメソッド

【使用例】
new Theater({
  scenario: [
    function(){
      this.prompt("なにかいれてね", "でふぉると");
    },
    function(result){
      this.alert("けっか:" + result);
    }
  ]
});

シナリオ(関数配列)内の関数で「this.confirm(表示文字列[, デフォルト値]);」として使用します。デフォルト値は省略可能です。指定文字列とテキストボックス、「入力」ボタンを表示します。「入力」ボタンを押すと、閉じて次の関数を実行します。このとき、関数の引数にテキストボックスに入力された値が設定されます。

上記3メソッドで表示されるメニューは、いずれも画像ボックス*3の中心に表示され、ドラッグ可能です。

これ以外のメニューを作成する方法については今後解説します。

とりあえず、例です。promptメソッドで名前入力を促し、未入力ならalertメソッドでメッセージを出して再入力を促します。入力ありならconfirmメソッドで入力を確認してもらい、「いいえ」が押されたら入力画面に戻ります。「はい」が押されたら次へ遷移します。

http://susie-t.seesaa.net/article/35350351.html

以下は上記例のブログ内で記述したコードです。(ファイルの読み込みはブログの共通設定で行っています。)

<button id='d20070306reset' class='reset'>
はじめから</button>
<div id="d20070306play" class='play'>
<div id="d20070306frame" class='frame'></div>
<div id="d20070306msg" class='msg'></div>
</div>
<script type="text/javascript">
var d20070306 = {}
d20070306.imgRoot = 'http://susie-t.up.seesaa.net/image/'
d20070306.img = {
  sample1: d20070306.imgRoot + 'sample1.gif',
  sample2: d20070306.imgRoot + 'sample2.gif',
  sample3: d20070306.imgRoot + 'sample3.gif'		
};
d20070306.scenario = [
  function (){
    this.work.name = "";
    this.act({
      img: d20070306.img.sample1,
      msg: '名前入力テスト'
    });
  },
  function (){
    this.act({
      img: d20070306.img.sample2,
      msg: 'お名前を入力してください'
    });
  },
  function (){
    this.push([
      function(){
        this.prompt(
          "お名前を入力してください",
          this.work.name
        );
      },
      function (result){
        this.work.name = result;
        if(result == ''){
          this.back();
          this.alert("お名前が入力されていません><;");
        }else{
          this.confirm("あなたのお名前:"
          + result + "<br/>よろしいですか?");
        }
      }
    ]);
    this.play();
  },
  function (result){
    if(result){
      this.act({
        img: d20070306.img.sample3,
        msg: "ようこそ" + this.work.name + "さん"
      });
    }else{
      this.back();
      this.play();
    }
  },
  function (){
    this.work.name = "";
    this.act({
      img: d20070306.img.sample1,
      msg: '名前入力テストおわり。'
    });
  }
];
new Theater({
  frameId: 'd20070306frame',
  msgId: 'd20070306msg',
  resetId: 'd20070306reset',
  playId: 'd20070306play',
  filter: ImageChanger.getFilter({no:10}),
  outEffect:'Fade',
  inEffect:'Appear',
  scenario: d20070306.scenario,
  img: d20070306.img
});
</script>

・・・説明しなきゃいけないことが多いな><;

Theaterクラスインスタンス作成時にframeId等を指定しています。無指定時はデフォルトで'frame'等が割り振られるのですが、同ページに複数Theaterクラスの利用があると、競合してしまうので個別のIDを振る必要があります。なので、いちいち設定しています。また、競合を避けるためd20070306オブジェクトを作成・利用しています。・・・分かりづらくてすみません。

Theaterクラスインスタンス作成時のoutEffect、inEffectオプションは、IE以外にも画像切替効果をつけようとして追加したオプションです。値はscriptaculousのEffectに準じています・・・が、全部が動くわけではありません^^; とりあえずoutEffect(消滅時)に'Fade'、innEffect(出現時)に'Appear'は設定できます・・・。詳しくは今後解説します。

pushメソッドはサブシナリオを設定するメソッドです。引数はシナリオ(関数配列)です。サブシナリオが終了すると、親のシナリオへ復帰します。

backメソッドは、シナリオを「ひとつ戻す」メソッドです。ただし、これにより必ずしも「直前のシナリオ関数」が実行できるわけではありません。やっていることは、当該シナリオのインデックスを減算*4するだけです。このため、サブシナリオから復帰した直後の場合、backメソッドを使用してもサブシナリオへは戻れません。

ここでは、この特性をシナリオ作成に利用しています。サブシナリオ直後に判定し、結果によってbackし、再度サブシナリオを実行させています。

back以外にも、シナリオ操作メソッドとしてjump(指定インデックスへ飛ぶ)、move(現インデックスから指定数減算・加算する)も用意していますが、この二つの使用はあまりお勧めできません。シナリオ修正が異常にしにくくなるためです。たとえば、上記のback+サブシナリオの方法であれば、サブシナリオ部分の修正はbackメソッドに影響しません。サブシナリオを使用せずにmoveメソッドを使用すると、戻る先からbackメソッド間でシナリオ関数の追加・削除が発生すると、moveの引数を変更する必要があります。そして、これは非常に気づきにくいです。jumpメソッドについても同様の理由です。

pushやbackは、それだけでは「シナリオを設定する」だけで、実行はしません。直後に実行したい場合はplayメソッドを呼び出します。ただし、各種メニューを呼び出した場合は、閉じると自動的に次のシナリオ関数が呼び出されます。

・・・この辺りの仕様は改善の余地があるかもしれません。

以上。あ゛ー。分かりにくいですねぇ・・・TT

なにかご質問がありましたらコメントORメールください・・・。

*1:他の操作ができないこと

*2:IEならwindow.showModalDialogを使うという手もあります。FireFoxではwindow.openの表示オプションでmodalが指定できるようです。(ただし特権の許可が必要とのこと。未確認)

*3:id=frameもしくはTheaterインスタンス作成時のオプションframeIdで指定されたIDの要素

*4:-2する。インデックスは常に次のシナリオ関数を指しているため