JavaScript Maniax

■RPGの戦闘シーンをつくる その2

多彩な攻撃パターンを再現。
前回の講義で作成したプログラムでは単純な殴り合い、しかも敵の方が圧倒的に強いということで欲求不満なサンプルを提示したところで終わってしまいました。
そこで貧弱な勇者氏には呪文を使えるようにしてあげましょう。
呪文に関しても基本的には攻撃と変わりありません。
引数として攻撃側や防御側のオブジェクトを渡し、ダメージなどを計算する関数をつくってやればよいのです。
//--sample--
function gira(at, df){
    var p,m,r;
    r=true;
    m=at.name+"は攻撃呪文を唱えた⇒";
    if(at.mp<1){
        m+="しかし魔力がたりない!";
    }else{
        at.mp--;
        //影響力計算
        p=Math.floor(Math.random()*4)+8;
        //メッセージ作成
        if(p>0){
            m+=df.name+"に"+p+"ポイントのダメージを与えた。";
            df.hp-=p;
            if(df.hp<1){
                //戦闘終了
                m+="<BR><FONT color=\"red\">"+at.name+"の勝利!(残り体力"+at.hp+")。</FONT>";
                r=false;
            }
        }else{
            m+=df.name+"には効かなかった!";
        }
    }
    m+="<BR>";
    document.all.t.innerHTML+=m;
    return r;
}
//--sample--
関数名に関してはかるく流しておいて、内容は通常攻撃と対して変わらないことが判るかと思います。
ただ、キャラクタのプロパティに魔力を追加してあります。
//--sample--
function chara(a,b,c,d,e,f){
    this.hp=a;      //体力
    this.mhp=a;     //最大体力
    this.mp=b;      //魔力
    this.sp=c;      //速さ
    this.at=d;      //攻撃力
    this.df=e;      //防御力
    this.name=f;    //名前
}
//--sample--
オブジェクトを実装するときに引数が増えたことを忘れないよう気をつけてください。
//--sample--
mych=new chara(val[0],val[1],val[2],val[3],val[4],"勇者");
enmy=new chara(50,50,20,40,40,"魔王");
//--sample--
回復魔法を作成することも考え、体力プロパティの初期値を最大体力というプロパティにも登録しました。
体力を回復しても、この最大値を超えないようにする為です。
回復魔法の関数も同じように簡単に作ることが出来ます。
//--sample--
function hoimi(at){
    var p,m;
    m=at.name+"は回復呪文を唱えた⇒";
    if(at.mp<1){
        m+="しかし魔力がたりない!";
    }else{
        at.mp--;
        //影響力計算
        p=Math.floor(Math.random()*8)+16;
        //メッセージ作成
        if(p>0){
            if(p>(at.mhp-at.hp))p=at.mhp-at.hp;
            m+=at.name+"の体力が"+p+"ポイントの回復した。";
            at.hp+=p;
        }else{
            m+="呪文は失敗した!";
        }
    }
    m+="<BR>";
    document.all.t.innerHTML+=m;
    return true;
}
//--sample--
回復時は防御キャラはいませんから、引数は呪文を唱えるほうのキャラクタオブジェクトだけで良いのが判るかと思います。

あとは、攻撃、攻撃魔法、回復魔法のどの関数を呼ぶかを決めればよいのです。
ゆくゆくは画面から入力した値にしたがって行動させるようにしようと思いますが、まずはランダムに関数を呼んでみます。
//--sample--
switch(Math.floor(Math.random()*10)){
case 0:case 1:case 2:case 3:case 4:case 5:flg=attack(mych, enmy);break;
case 6:case 7:flg=gira(mych, enmy);break;
default:flg=hoimi(mych);break;}
//--sample--
敵も「魔王」を名乗るわりにはタコ殴り魔王なので、攻撃魔法を使うようにしてみました。
こちらもゆくゆくは勇者の残り体力をみて攻撃パターンを変える様なプログラムにしてゆきたいと思います。
//--sample--
switch(Math.floor(Math.random()*10)){
case 0:case 1:case 2:case 3:case 4:case 5:flg=attack(enmy, mych);break;
default:flg=gira(enmy, mych);break;}
//--sample--
これで勇者も敵も魔法を使う、手に汗握る激戦(言いすぎ)が繰り広げられるようになりました。

[
sample-a] [DL]

攻撃、回復の他にも、火を噴いたり攻撃力を増したり、いろんな特殊攻撃が同じようなプログラムで実装できます。
加工してみてください。



応用編

折角作ったサンプルですが、ボタンを押すと同時に結果が表示されてしまい、しかも戦闘が長引くと画面スクロールが必要になったりと臨場感がありません。
実際のRPGのように、少しづつ結果が表示されてゆく形にしたいと思います。

まず表示にウェイトをもたせるには、main関数の中に長期ループを入れてしまう方法もあるのですが、それだとやはりPCに負荷がかかってしまいます。また制御が戻ってくるまでブラウザに対し操作ができなくなってしまう欠点があります。
そこで、以前から使っているsetTimeoutをつかってmain関数を再帰的に呼び出す方法を使います。
//--sample--
var af,mych,enmy,flg;
af=0;

//メイン処理
function main(){
    var m,kbn;
    kbn=0
    //攻撃内容選択
    if(af==0){
        switch(Math.floor(Math.random()*10)){
        case 0:case 1:case 2:case 3:case 4:case 5:m=mych.name+"の攻撃 ⇒ ";kbn=0;break;
        case 6:case 7:m=mych.name+"は攻撃呪文を唱えた ⇒ ";kbn=1;break;
        default:m=mych.name+"は回復呪文を唱えた ⇒ ";kbn=2;break}
    }else{
        switch(Math.floor(Math.random()*10)){
        case 0:case 1:case 2:case 3:case 4:case 5:m=enmy.name+"の攻撃 ⇒ ";kbn=0;break;
        default:m=enmy.name+"は攻撃呪文を唱えた ⇒ ";kbn=1;break}
    }
    if(document.all.t.innerHTML!="")m="<BR>"+m;
    setMess(m);
    if(flg){
        window.setTimeout("sub(" + kbn + ")",1000)
    }else{
        document.f.s.disabled=false
    }
}

//サブ処理
function sub(kbn){
    //攻防
    if(af==0){
        switch(kbn){
        case 0:flg=attack(mych, enmy);break;
        case 1:flg=gira(mych, enmy);break;
        default:flg=hoimi(mych);break;}
        af=1;
    }else{
        switch(kbn){
        case 0:flg=attack(enmy, mych);break;
        default:flg=gira(enmy, mych);break;}
        af=0;
    }
    document.all.p.innerHTML=mych.name+":"+mych.hp+" ---- "+enmy.name+":"+enmy.hp;
    if (flg) {
        window.setTimeout("main()",2000)
    }else{
        document.f.s.disabled=false
    }
}
//--sample--
これまでの様に関数内ループで処理を進めていたときと違って、変数をグローバル化するのを忘れないようにして下さい。
どちらの攻撃かという情報もグローバル変数afに持たせています。
複雑な点としては、メイン処理のsetTimeoutでサブ処理、サブ処理のsetTimeoutでメイン処理を呼び出しています。
これは、メイン処理で乱数を発生させどの処理を行うかを確定し、サブ処理で演算をおこなっているためです。
なぜこんな面倒なことをしているかというと、それぞれのターンだけでなく、「○○の攻撃」といった攻撃内容と「○○ポイントのダメージを与えた」という攻撃結果の表示にも時間差を持たせたかったからです。

また、同時に表示する行数を5行以内にまとめました。
これは、下記関数で実装しています。
//--sample--
function setMess(m){
    var mf;
    m=document.all.t.innerHTML+m;
    mf=m.split("<BR>");    //splitは、文字列を指定文字で区切り配列に格納する関数
    if(mf.length>5){
        m="";
        for(var i=mf.length-5;i<mf.length;i++){
            if(i!=mf.length-5){
                m=m+"<BR>";
            }
            m=m+mf[i];
        }
    }
    document.all.t.innerHTML=m;
}
//--sample--
現在の表示内容を<BR>で区切り、下5行だけ表示させています。
処理内容は同じなのに、見せ方一つでゲームとしての臨場感がかなり変わったのがわかるかと思います。

[sample-b] [DL]
[top] [index]
トップメニュー 基礎編 実践編 外部リンク