見出し画像

【定期ゲ製作】戦闘が発生して、進行して、終わるということ

定期ゲーを作ろうとなったとき、まずすべきことを記しておきます。もちろん、この先には無限の荒野が広がっています。ただ、最初に作るべきは決まっていると思っています

それこそが、戦闘処理です。戦闘のない定期ゲーは間違いなく超上級者向けであり、最初から作るものではなく、最初に作って損のない、絶対使う処理というのは戦闘処理であることが分かります

「俺はゲーム製作でオリジナル戦闘処理組めるから心配いらないぜ!」という方には物足りない記事かもしれません。ただ、戦闘処理は万人が組めるものではないので、ここに記しておこうと思います

仕様を決めよう

分かりづらい複雑なシステムは、慣れてきたころに作りましょう。ファミリーコンピューターでも作れそうな戦闘仕様を想像します

①パーティ制ではなく、プレイヤーキャラ一人が戦う
②スキル設定は難しいので、スキルがランダムに発動する
③敵は一人。全体対象などの対象指定などが面倒なため
④ステータスは4つ+HP 。片手で数えられるため

すごく分かりやすいですね。さっそく、作ってみましょう。今回の言語はperlですが、何でもいいです。あくまで一例なので、もっと別な戦術を組ませるぜ!という方は、note待ってます

戦場を決めよう

戦場、というのはプレイヤーやNPCを登録するリストです。戦闘参加者名簿だと思ってください。これは二次配列で作ります。霧のゲームでは、life_list配列というものです

life_listの最初の配列には、idを登録します。これは、登録番号以外の意味を持ちません。そして、life_list[$id][0]、つまり登録番号が割り振られた名簿の最初に、Enoを格納します。Enoは仮に1番とします

$life_list[1][0] = 1;

これで、Eno1番のプレイヤーが登録番号1番を背負って戦場に参戦しました。戦闘処理はEnoごとに発生するため、引数で$enoを指定して、

$life_list[1][0] = $eno;

これでOKです。次は敵ですね。敵を登録番号2番で参戦させてもいいのですが、仕様変更で地獄を見るため、領域を分けます

$life_list[51][0] = -1;

51以上の登録番号を、全て敵にします。50未満は味方にします。そうすれば、登録番号で敵味方を区別できます。いつか、40キャラとか敵味方にあふれる戦闘もできるでしょう

代入したEno-1は、敵NPCを意味します。Enoが負の値であることで、NPCということが一瞬で判別できます。敵データ1番にスライムでも登録して、Enoに-1をかけて、データへのインデックスにできます

ステータスを決めよう

ただ登録しただけでは、もちろん戦えません。ステータスが必要です。今回はステータスを

攻撃
防御
魔法
耐性

の4つとします

攻撃 - 防御 / 2 = ダメージ
魔法 * 2 - 耐性 = スキルダメージ
攻撃+魔法の高い順に攻撃
防御+耐性 = HP

という式で行きます。適当に。ここは本当適当に。上手なバランスよりも、動きやすそうなTシャツ短パンを重視してください

では、それをlife_listに割り振っていきましょう

[0]  =  eno
[1]  =  HP
[2]  =  最大HP
[10] = 攻撃
[11] = 防御
[12] = 魔法
[13] = 耐性

 これらのパラメータを増減すれば、戦闘になります。さぁ、初期値を設定して、次は戦闘ループを組みましょう

戦闘ループ

まず、大きな枠を作ります。戦闘の大きな枠と言えば、ターンですね

my $win_flag = 0;
for (my $turn=1;$turn<31;$turn++){
    # ターン内処理
}

これが大枠です。30ターンまで経過したら、戦闘が終わります。その中身が問題なんだよォ!! わかります。次へ行きます

まず、早い順に行動することが仕様から分かっていますね。どうすればいいんでしょう?

こたえ

まず、戦場をループさせて、全員の「id」と「素早さ」を取得します

my @spd_list = ( );
my $spd_id = 0;
for (my $id=0;$id<100;$id++){
    unless($life_list[$id][0]){next;}
    my $spd = $life_list[$id][10] + $life_list[$id][12];
    $spd_list[$spd_id][0] = $id;
    $spd_list[$spd_id][1] = $spd;
    $spd_id++;
}
@spd_list= sort {$b -> [1] <=> $a -> [1]} @spd_list;

ワッとソースを吐いたので、一つずつ解説します。

my @spd_list = ( );
my $spd_id = 0;

ここで、速さの比較を行うために二次配列の素を用意します。spd_idを別に用意したのは、分かりやすくするためです

for (my $id=0;$id<100;$id++){
    unless($life_list[$id][0]){next;}

最強に活用する構文です。これは、戦場の全員に処理を行う(ただし空欄は除く)という意味を持ちます。戦場の全員から素早さを獲得するために、使用されています

    my $spd = $life_list[$id][10] + $life_list[$id][12];
    $spd_list[$spd_id][0] = $id;
    $spd_list[$spd_id][1] = $spd;
    $spd_id++;

これは、素早さを求めて、素早さのリストに入れている図です。一緒にidも格納しているのは、誰のspdだか分かりやすくするためです

@spd_list= sort {$b -> [1] <=> $a -> [1]} @spd_list;

ここで、全員からアンケートを取った素早さのリストを、素早さの高い順に並べ替えています。これで同時にidも並べ替えられています

次は、素早さ順に「攻撃処理」を回します

攻撃処理

さて、素早さのリストが取れたので、それの順に従って攻撃を開始しましょう

for (my $act_id=0;$act_id<=$#spd_list;$act_id++){
    my $id = $spd_list[$act_id][0];
    @life_list = &atk_unit($id, \@life_list);
}

配列の動きに詳しければ、見えてきますね。先ほど作った素早さのリスト順にidを取得して、idを主体として攻撃処理(atk_unit)を行う、ということです

その中身とは、いったい……!!

今回は、超適当に組みます

sub atk_unit{
    my $atk_id = shift;
    my $ref_life = shift;
    my @life_list = @{$ref_life};
    # 無効なid
    unless ($atk_id){return @life_list;}
    # 敵を対象に取る
    my $ene_id = 51;
    if ($atk_id > 50){$ene_id = 1;}
    # スキルが発動するかどうか判定する
    my $skill_dice = int(rand 3);
    # ダメージを計算する
    my $dmg = 1;
    # スキルと通常攻撃で分岐する
    if ($skill_dice == 0){
        $dmg = ($life_list[$atk_id][12] * 2) - $life_list[$ene_id][13];
    }
    else {
        $dmg = $life_list[$atk_id][10] - int($life_list[$ene_id][13] / 2);
    }
    # HP減少処理のために、ダメージを負の値にする
    $dmg *= -1;
    # ダメージを与える
    @life_list = &hp_calc($ene_id, $dmg, \@life_list);
    return @life_list;
}

一つずつ解説します

    my $atk_id = shift;
    my $ref_life = shift;
    my @life_list = @{$ref_life};
    # 無効なid
    unless ($atk_id){return @life_list;}

これは引数を揃えて、無効な引数を弾いています

    # 敵を対象に取る
    my $ene_id = 51;
    if ($atk_id > 50){$ene_id = 1;}
    # スキルが発動するかどうか判定する
    my $skill_dice = int(rand 3);
    # ダメージを計算する
    my $dmg = 1;
    # スキルと通常攻撃で分岐する
    if ($skill_dice == 0){
        $dmg = ($life_list[$atk_id][12] * 2) - $life_list[$ene_id][13];
    }
    else {
        $dmg = $life_list[$atk_id][10] - int($life_list[$ene_id][13] / 2);
    }

この辺は今回簡略化してマジックナンバーで処理していますが、いくらでも改良改善できるはずです

    # HP減少処理のために、ダメージを負の値にする
    $dmg *= -1;
    # ダメージを与える
    @life_list = &hp_calc($ene_id, $dmg, \@life_list);

HP増減処理はよく使うので、別の関数にしています。ゲーム製作の基本ですね

sub hp_calc{
    my $id = shift;
    my $val = shift;
    my $ref_life = shift;
    my @life_list = @{$ref_life};
    # 無効なid
    unless ($id){return @life_list;}
    $life_list[$id][1] += $val;
    # 最大HPを越えてはいけない
    if ($life_list[$id][1] > $life_list[$id][2]){
        $life_list[$id][1] = $life_list[$id][2];
    } 
    return @life_list;
}

HPを増減させるだけなので、説明不要かと思われます

戦闘を終えるために

このままでは永久にHPが減り続けるだけです。終わりがあります。それを組みます

my $win_flag = 0;
for (my $turn=1;$turn<31;$turn++){
    # ターン内処理
    # ~素早さのリスト化から攻撃まで~
    $win_flag = &end_ck(\@life_list);
    $if ($win_flag != 0){last;}
}

$win_flag は負の値だと負け、正の値だと勝ちとします。end_ck 関数の中身はというと

sub end_ck{
    my $ref_life = shift;
    my @life_list = @{$ref_life};
    my $atk_alive = 0;
    my $ene_alive = 0;
    my $ans = 0;
    for (my $id=0;$id<100;$id++){
        unless($life_list[$id][0]){next;}
        if ($life_list[$id][1] <= 0){next;}
        if ($id < 50){$atk_alive++;}
        else{$ene_alive++;}
    }
    if ($atk_alive > 0 && $ene_alive == 0){$ans = 1;}
    elsif ($atk_alive == 0 && $ene_alive > 0){$ans = -1;}
    elsif ($atk_alive == 0 && $ene_alive == 0){$ans = -2;}
    return $ans;
}

全員の生存者数を計測して、判定して、答えを返す。単純ですね。これで、ゲームの戦闘処理の最低限が完成したということです

残すは細かいところです。ログをどうやって出力するのかとか、ステータスはどうやって計算するのかとか、敵とどうエンカウントするのかとか、計算式とか、そういう細かいところは後にして、戦闘処理を回しましょう

そうすれば、結構ニコニコできるはずです。自分の作った人形が動いて、人形劇を見せてくれるのですから

戦闘処理。それがあなたの原動力になります。ひとは戦うために生まれてきたのですから

無限の戦いの中で、君は何を見つけ、どこへ辿り着くのか――

定期更新型ネットゲーム、グレムリンズ・ギフトは来月再始動予定です

この記事が気に入ったらサポートをしてみませんか?