こんばんは。


前回のシーン管理に関して、ユーザー入力を待つ場面について色々と考えたので、ここに書き残しておきたいと思います。


今、とりあえず「はい/いいえ」の選択をユーザーに促す場面を考えます。このとき、この場面を関数化して、結果を戻り値とすることが一応は可能です。概念としては


boolean yesNo(){


while(true){


//入力処理


//描画処理


if(FIRE)


break;


sleep(1000);


}


return result;


}


相当適当に描きましたが、ユーザーから入力を待つ間実行を止める行為に近いものがあります。


上で一応と書いたのは、いくつかの条件を満たしていないと不具合が生じるためです。その一つは他に並行処理がないことなのはすぐに分かると思います。


これを拡張していけば、アイテムなど複数項目からの選択も同様に記述できるでしょう。更に、構造に気をつければこれがスタックとして応用できることも分かるでしょう。


たとえばウィンドウの表示位置、項目名、ウィンドウスタイル等を引数として渡してやれば、複数のウィンドウスタックを場合分けなどせず簡単に実装することができます。


とはいえ、メインループが常に同じところを通過しないというのはあまり気持ちいいことではありません。便利なだけに実装には最新の注意を払いたいものですね。





はっきり言って、見た目と操作性が同じならば、ユーザーにとってプログラムの構造などどうでもいいことです。しかし、保守・開発する我々プログラマーにとっては非常に重要なものです。





この方法が皆さんの助けになり、バグを発生させないことを祈ります。



こんばんは。



今回は、戦闘におけるシーン管理の基礎について述べたいと思います。

これは戦闘に限らず、あらゆる場面で使える、非常に有用なテクニックです。



 さて、皆さんが普通にプログラミングをしている限り、必ずと言っていいほどif-else構文を使うと思います。switch-case構文でも構いません。とにかく、条件によって処理を分岐させるということは、単純なバッチ処理やトイ・プログラムでない限り必要な手続きであります。



 ことゲームにおいては、「シーン」は多種多様に渡ります。タイトル、移動、戦闘、売買……どこで大きなシーンを分けるかについては、仕様と個人の嗜好が大きく影響してくるので、とくに議論するつもりはありません。

このシーンというものを一つの処理のまとまりとして見た時、条件分岐によって処理を変えるという手法は、目的に合致していると思います。すなわち、いわゆるメインループにおいて、今どのシーンを実行しているかという変数の値に応じて、処理を分岐するということです。これはごく普通の考えで、皆さんも意識せずやっていると思います。

 簡単なテクニックながら、「サブシーン」を導入するというのは非常に効果的です。変数が一つの場合に、0、1、2…のうち1と2の間に入れるのがリーズナブルなシーンが出てきた時、それが15や-1に入ってしまうという間抜けな問題を解決してくれます。法律でよくある~条の2、というやつです。シーンが1の1、1の2、のように分けられれば、今まで数直線上で管理されていたシーンが平面的な広がりを持って、柔軟に管理できることが分かると思います。



 それとは別に、シーン管理の構造には要注意です。皆さんは各シーンをそれぞれクラスとして作りますか?関数に分けて書きますか?それとも、while(true)内に上から順に書いていきますか?

 どの方法でも同じように実装できると思いますが、一つだけ異なる点があります。変数のスコープに関わる部分です。シーンそれぞれを関数に分ける方法は非常に有用で、私もこの方法を採用しています。しかし今ユーザーに「はい」と「いいえ」について入力を待つ場面だとして、その値を保持する変数は関数の中で宣言できません。その変数の寿命が1ループしかないからです。結局この方法では、関数の外にこの瑣末な変数を記述する必要があります(なぜ瑣末かと言えば、この選択の過程を表す変数は、過程を表す画面でしか必要とされないからです)。

 このような変数は全体を通して30個にも満たない数でしょうが、構造として美しいとは言えないでしょう。とはいえ、この部分は他と違って後から修正することが容易ですし、機能的にはさしたる違いはありません。自分の意識として、「なぜこの構造が適しているか」を考えながらコーディング出来ればそれで十分だと思います。「自分が書きやすいから」というのも大きな利点なのです。






こんばんは。


今回は、前回実装したルーレット選択を利用した敵エンカウントの実装例を見ていきたいと思います。


int[] enemy_rate = {80,15,5};


public void encount(){

  if(rand.nextInt(100) < enemy_emergence_rate){

  //敵の出現が確定

     for(int i = 0;i <encount_enemy.length; i++)

      encount_enemy[i] = 0;



    //出現数を確定

    enemylength = rouletteChoice(enemy_rate)+1;

    for(int i = 0;i < enemylength; i++)

      encount_enemy[i] = map_enemy_lib[rouletteChoice(map_enemy_rate)];



    nextScene = SC_BATTLE;

    startAnimation(0);

  }

}


そのまんま持ってきたので、いくつか未知の関数・変数があると思いますが…


大まかな流れとしては、encount_enemy[]の中にエンカウントした敵のIDを入れ、次のシーンを戦闘として指定し、アニメーションを開始することで戦闘を実装しています。encount_enemy[]が0のところは、敵がいないことを意味します。


int型のenemy_emergence_rateは、いわゆる「エンカウント率」です。enemy_rateは一匹、二匹、三匹の敵と出会う敵の確率を意味しています。


map_enemy_lib[]はそのマップで出会う敵のIDを保存したint配列で、map_enemy_rate[]はそれに対応した確率を表すint配列です。ルーレット選択で敵の種類を選択しています。





ざっとこんな感じです。ルーレット選択を関数にしておくと実にスマートに表現出来る事がご理解頂けたのではないでしょうか。


尚、enemy_emergence_rateをenemy_rateの先頭に加え、0匹、1匹…の確率にすることも可能です。そうしなかったのは、「敵の出現率」というものを直感的に操作できるようにしたかったのと、エンカウント方法として単純なランダム以外の方法を採用するということも考慮したため、というシンプルな理由です。


他のエンカウント方法としては、あらかじめ次の敵を出現させるまでの歩数を決めておく、という手法が有名です。この方法の方が、敵の出現頻度がばらつかず、ユーザーにストレスが少ない場合が多いという利点があります。