命中率補正とは
ダメージ計算や行動順判定には補正(ランク)を計算した結果を反映していましたが、命中率補正についてはまだ未実装だったため、今回はこちらを作成していきたいと思います。
まずはwikiを参考に、計算方法を見ていきましょう。
命中(ポケモンwiki)
第三世代以降
A=技の命中率×ランク補正
ランク補正=自分のランク補正(命中率)-相手のランク補正(回避率)
他のランクと異なり、命中率の補正には「命中率」と「回避率」を計算する必要があります。単純にその差分を求めるだけですが、補正は他と同様に(ー6)〜(+6)の13段階のため、差分を求めて最大値・最小値に修正してから倍率を求めていきます。
命中率
ではまず命中率について見ていきましょう。
命中率(ポケモンwiki)
命中ランクを減少させる技は初代の中でもいくつかありますが、上昇させることができるのは「ヨクアタール」というアイテムのみで技は存在しません。
今回は、命中率を下げる技の検証用として「すなかけ」を使って検証します。
すなかけ(/Classes/Move/SandAttack.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// すなかけ
class SandAttack extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'すなかけ';
/**
* 説明文
* @var string
*/
protected $description = '相手の命中率を1段階下げる。';
/**
* タイプ
* @var string
*/
protected $type = 'Ground';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'status';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 15;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* 追加効果
*
* @param array $args
* @return void
*/
public function effects(...$args)
{
/**
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
*/
list($atk, $def) = $args;
// 相手の命中ランクを1段階下げる
$msg = $def->subRank('Accuracy', 1);;
$this->setMessage($msg);
}
}
回避率
次に回避率について見ていきましょう。
回避率(ポケモンwiki)
こちらは命中率とは異なり、上昇させる技は初代でもいくつかある中、減少させる技がありません。
回避率を検証するための技には「かげぶんしん」を使用しましょう。
かげぶんしん(/Classes/Move/DoubleTeam.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// かげぶんしん
class DoubleTeam extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'かげぶんしん';
/**
* 説明文
* @var string
*/
protected $description = '自分の回避率を1段階上げる。';
/**
* タイプ
* @var string
*/
protected $type = 'Normal';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'status';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = null;
/**
* 使用回数
* @var integer
*/
protected $pp = 15;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* 追加効果
*
* @param array $args
* @return void
*/
public function effects(...$args)
{
/**
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
*/
list($atk, $def) = $args;
// 自分の回避率ランクを1段階上げる
$msg = $atk->addRank('Evasion', 1);
$this->setMessage($msg);
}
}
命中判定への導入
それでは、ランク補正で算出した倍率を命中率に反映させましょう。ランク補正値は以下の通りです。
ランク | -6以下 | -5 | -4 | -3 | -2 | -1 | 0 | +1 | +2 | +3 | +4 | +5 | +6以上 |
倍率 | 2/8 | 2/7 | 2/6 | 2/5 | 2/4 | 2/3 | 2/2 | 3/2 | 4/2 | 5/2 | 6/2 | 7/2 | 8/2 |
※ランク = 自分の命中ランク – 相手の回避率ランク
こうげき等のランク補正で算出したものと同じ式を使って倍率を求めていきます。詳細は以下の記事を参考にしてください。
ランク補正をかける箇所についてですが、一撃必殺技の命中率算出には補正がかかりません。なので、通常技の命中率計算でのみ補正がかかるよう、分岐内に記述しましょう。
攻撃用トレイト(/Traits/Service/Battle/ServiceBattleAttackTrait.php)
/**
* 命中判定
*
* @param object $atk
* @param object $def
* @param object $move
* @return boolean
*/
private function checkHit($atk, $def, $move)
{
// 一撃必殺技のチェック
if($move->getOneHitKnockoutFlg()){
if($atk->getLevel() < $def->getLevel()){
// 相手の方がレベルが高ければ無効
$this->setMessage($move->getOneHitKnockoutFailedMessage($def->getPrefixName()));
return false;
}
// レベル差計算を含めた命中率を取得
$accuracy = $move->getOneHitKnockoutAccuracy($atk, $def);
}else{
// 命中率取得
$accuracy = $move->getAccuracy();
// nullの場合は命中率関係無し
if(is_null($accuracy)){
return true;
}
/**
* ランク補正
* 攻撃側の命中率 - 防御側の回避率
*/
$rank = $atk->getRank('Accuracy') - $def->getRank('Evasion');
if($rank > 0){
// プラス補正
if($rank > 6){
// 最大値丸め
$rank = 6;
}
$per = (3 + $rank) / 3;
}else{
// マイナス補正
if($rank < -6){
// 最小値丸め
$rank = -6;
}
$per = 3 / (3 - $rank);
}
// 倍率を切り捨てしてランク補正込みの命中率を算出
$accuracy *= round($per, 2);
}
// カウンターの失敗判定
if((get_class($move) === 'Counter') && empty($atk->getTurnDamage('physical'))){
// 自身にこのターン物理ダメージが蓄積していなければ失敗
$this->setMessage($move->getFailedMessage($atk->getPrefixName()));
return false;
}
/**
* 0〜100からランダムで数値を取得して、それより小さければ命中
* 例:命中80%→mt_randで60が生成されたら成功、90なら失敗
*/
if($accuracy >= mt_rand(0, 100)){
// 攻撃成功
return true;
}
// 攻撃失敗
$this->setMessage($move->getFailedMessage($atk->getPrefixName()));
return false;
}
checkHitメソッド内に追記しました。これで技の命中率に対して補正を掛けた値を命中率として判定を行うことができます。
それでは「すなかけ」と「かげぶんしん」を使って検証してみましょう。
すなかけ
かげぶんしん
「しっぽをふる」と「なきごえ」はともに命中率が100%の技です。なので、外れる判定が出ていればしっかりと回避率・命中率補正が入って判定がなされていることがわかります。
もし正確な数値を確認したい人は、算出した命中率をvar_exportやvar_dumpを使って出力してみてください。
交代技とは
数は限られていますが、ポケモンには「交代技」というものが存在します。最新世代のレーティングバトルでも採用されている戦略的な技のひとつです。
ポケモンチェンジ(ポケモンwiki)
ポケモンを交代させる技にもいくつか種類がありますが、その中でも初代からある技は「ふきとばし」と「ほえる」の2つです。
「ふきとばし」と「ほえる」
この2つの技は基本的に同じ効果を持っています。特性などの関係上「ふきとばし」の方が優先的に採用されていますが、現段階でのPHPポケモンにおいては関係がありませんので、どちらも同等の技となります。
それでは、今回は検証用として「ふきとばし」を作成しましょう。
ふきとばし(/Classes/Move/Whirlwind.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// ふきとばし
class Whirlwind extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'ふきとばし';
/**
* 説明文
* @var string
*/
protected $description = '';
/**
* タイプ
* @var string
*/
protected $type = 'Normal';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'status';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = null;
/**
* 使用回数
* @var integer
*/
protected $pp = 20;
/**
* 優先度
* @var integer
*/
protected $priority = -6;
/**
* 追加効果
*
* @param array $args
* @return void
*/
public function effects(...$args)
{
/**
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
*/
list($atk, $def) = $args;
// バトル終了
$this->setMessage($def->getPrefixName().'は吹き飛ばされた');
$this->setEmptyMessage('battle-end');
$this->setResponse(true, 'end');
}
}
※今回交代効果はeffectsメソッド内に記述していますが、こちらはバトルの仕様に合わせて変更となる可能性があります(トレーナー戦・ポケモンが複数所有できるなどの機能追加)
他の技と異なるポイントは、バトル強制終了用の空メッセージと、終了判定用のレスポンスをセットしている点です。こちらは後ほど別に判定箇所を設けます。
次に命中率判定についてです。こちらはポケモンwiki「ふきとばし」のページの技の仕様で記述があります。
ふきとばし(ポケモンwiki)
第五世代以降では、野生ポケモン戦では使用者よりレベルが高い相手に対するふきとばしは失敗する。トレーナー戦ではレベルに関係なく当たれば必ず交代させることができる。
現段階では野生ポケモンとのバトルしか存在していませんので、レベル差による命中判定のみを導入しましょう。この判定が採用されるのは「ふきとばし」と「ほえる」のみなので、カウンターを実装したときと同様に技クラス指定で判定を行います。
攻撃用トレイト(/App/Traits/Services/Battle/ServiceBattleAttackTrait.php)
/**
* 命中判定
*
* @param object $atk
* @param object $def
* @param object $move
* @return boolean
*/
private function checkHit($atk, $def, $move)
{
// ふきとばし・ほえるのチェック
if(in_array(get_class($move), ['Whirlwind', 'Roar'], true)){
if($atk->getLevel() < $def->getLevel()){
$this->setMessage($def->getPrefixName().'は平気な顔をしている');
return false;
}
}
checkHitメソッドの最初に判定を追加しました。レベル差を比較して失敗時にはそれに合わせたメッセージを返却しています。
バトル強制終了
それでは、ふきとばしのeffectsメソッドに記述したバトルの強制終了を受け取り判定する処理を追加しましょう。こちらは、サービス内に分岐を追加します。
「たたかう」のサービス
/**
* 行動順に攻撃処理
*
* @return boolean (false: ひんしポケモン有り)
*/
private function actionAttack($orders)
{
foreach($orders as list($atk, $def, $move)){
// 攻撃ポケモンの怒り解除
$atk->releaseSc('ScRage');
// 攻撃
$this->attack($atk, $def, $move);
// バトル終了のレスポンスチェック(交代技など)
if($this->getResponse('end')){
break;
}
// ひんしチェック
$this->fainting = [
$atk->getPosition() => $this->checkFainting($atk),
$def->getPosition() => $this->checkFainting($def),
];
// どちらかがひんし状態なら処理終了
if($this->fainting['friend'] || $this->fainting['enemy']){
$result = false;
break;
}
} # endforeach
// 結果返却
return $result ?? true;
}
actionAttackメソッドで攻撃メソッドを呼び出した後、バトル終了判定が返ってきているかをチェックしています。「ふきとばし」や「ほえる」は優先度―6の技なので、基本的に後攻となりますが、相手も同優先度の技を使用していればこのタイミングでbreakしておかなければ內部でダメージ計算が行われてしまいます。
それでは「ふきとばし」を使って検証してみましょう。
高レベルの相手
低レベルの相手
高レベルの相手には失敗しましたが、低レベルの相手にはヒットしてバトルを強制終了することができました。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「命中率補正」と「交代技」についてご紹介しました。
現在プログラミング学習に取り組んでいる方や、ゲームづくりに興味がある人は、ぜひ参考にしてみてくださいね。