【定期ゲ製作】戦闘が発生して、進行して、終わるということ
定期ゲーを作ろうとなったとき、まずすべきことを記しておきます。もちろん、この先には無限の荒野が広がっています。ただ、最初に作るべきは決まっていると思っています
それこそが、戦闘処理です。戦闘のない定期ゲーは間違いなく超上級者向けであり、最初から作るものではなく、最初に作って損のない、絶対使う処理というのは戦闘処理であることが分かります
「俺はゲーム製作でオリジナル戦闘処理組めるから心配いらないぜ!」という方には物足りない記事かもしれません。ただ、戦闘処理は万人が組めるものではないので、ここに記しておこうと思います
仕様を決めよう
分かりづらい複雑なシステムは、慣れてきたころに作りましょう。ファミリーコンピューターでも作れそうな戦闘仕様を想像します
①パーティ制ではなく、プレイヤーキャラ一人が戦う
②スキル設定は難しいので、スキルがランダムに発動する
③敵は一人。全体対象などの対象指定などが面倒なため
④ステータスは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;
}
全員の生存者数を計測して、判定して、答えを返す。単純ですね。これで、ゲームの戦闘処理の最低限が完成したということです
残すは細かいところです。ログをどうやって出力するのかとか、ステータスはどうやって計算するのかとか、敵とどうエンカウントするのかとか、計算式とか、そういう細かいところは後にして、戦闘処理を回しましょう
そうすれば、結構ニコニコできるはずです。自分の作った人形が動いて、人形劇を見せてくれるのですから
戦闘処理。それがあなたの原動力になります。ひとは戦うために生まれてきたのですから
無限の戦いの中で、君は何を見つけ、どこへ辿り着くのか――
定期更新型ネットゲーム、グレムリンズ・ギフトは来月再始動予定です
この記事が気に入ったらサポートをしてみませんか?