状態異常の実装
今回は、前回作成した「まひ」を参考に、「ひんし」を除いた残りの状態異常も実装していきます。
クラスの作成
まずはそれぞれのクラスを作成します。前回解除時のメッセージを設定出来ていなかったので、まひと合わせて実装していきましょう。
状態異常:やけど(/Classes/StatusAliment/SaBurn.php)
<?php
require_once(__DIR__.'/../StatusAilment.php');
// やけど
class SaBurn extends StatusAilment
{
/**
* 正式名称
* @var string
*/
protected $name = 'やけど';
/**
* 状態異常にかかった際のメッセージ
* @var string
*/
protected $sicked_msg = '::pokemonは、やけどを負った';
/**
* すでにこの状態異常にかかっている際のメッセージ
* @var string
*/
protected $sicked_already_msg = '::pokemonは、既にやけどしている';
/**
* ターンチェック時に表示されるメッセージ
* @var string
*/
protected $turn_msg = '::pokemonは、やけどのダメージを受けている';
/**
* 回復時に表示されるメッセージ
* @var string
*/
protected $recovery_msg = '::pokemonは、やけどが治った';
}
状態異常:こおり(/Classes/StatusAilment/SaFreeze.php)
<?php
require_once(__DIR__.'/../StatusAilment.php');
// こおり
class SaFreeze extends StatusAilment
{
/**
* 正式名称
* @var string
*/
protected $name = 'こおり';
/**
* 状態異常にかかった際のメッセージ
* @var string
*/
protected $sicked_msg = '::pokemonは、氷漬けになった';
/**
* すでにこの状態異常にかかっている際のメッセージ
* @var string
*/
protected $sicked_already_msg = '::pokemonは、既に凍っている';
/**
* ターンチェック時に表示されるメッセージ
* @var string
*/
protected $turn_msg = '::pokemonは、凍ってしまって動けない';
/**
* 回復時に表示されるメッセージ
* @var string
*/
protected $recovery_msg = '::pokemonの氷が溶けた';
}
状態異常:ねむり(/Classes/StatusAilment/SaSleep.php)
<?php
require_once(__DIR__.'/../StatusAilment.php');
// ねむり
class SaPoison extends StatusAilment
{
/**
* 正式名称
* @var string
*/
protected $name = 'ねむり';
/**
* 状態異常にかかった際のメッセージ
* @var string
*/
protected $sicked_msg = '::pokemonは、眠ってしまった';
/**
* すでにこの状態異常にかかっている際のメッセージ
* @var string
*/
protected $sicked_already_msg = '::pokemonは、既に眠っている';
/**
* ターンチェック時に表示されるメッセージ
* @var string
*/
protected $turn_msg = '::pokemonは、ぐうぐう眠っている';
/**
* 回復時に表示されるメッセージ
* @var string
*/
protected $recovery_msg = '::pokemonは、目を覚ました';
}
状態異常:どく(/Classes/StatusAilment/SaPoison.php)
<?php
require_once(__DIR__.'/../StatusAilment.php');
// どく
class SaPoison extends StatusAilment
{
/**
* 正式名称
* @var string
*/
protected $name = 'どく';
/**
* 状態異常にかかった際のメッセージ
* @var string
*/
protected $sicked_msg = '::pokemonは、毒を浴びた';
/**
* すでにこの状態異常にかかっている際のメッセージ
* @var string
*/
protected $sicked_already_msg = '::pokemonは、既に毒に侵されている';
/**
* ターンチェック時に表示されるメッセージ
* @var string
*/
protected $turn_msg = '::pokemonは、毒のダメージを受けている';
/**
* 回復時に表示されるメッセージ
* @var string
*/
protected $recovery_msg = '::pokemonの毒は綺麗サッパリ無くなった';
}
状態異常:もうどく(/Classes/StatusAilment/SaBadPoison.php)
<?php
require_once(__DIR__.'/../StatusAilment.php');
// もうどく
class SaBadPoison extends StatusAilment
{
/**
* 正式名称
* @var string
*/
protected $name = 'もうどく';
/**
* 状態異常にかかった際のメッセージ
* @var string
*/
protected $sicked_msg = '::pokemonは、猛毒を浴びた';
/**
* すでにこの状態異常にかかっている際のメッセージ
* @var string
*/
protected $sicked_already_msg = '::pokemonは、既に毒に侵されている';
/**
* ターンチェック時に表示されるメッセージ
* @var string
*/
protected $turn_msg = '::pokemonは、毒のダメージを受けている';
/**
* 回復時に表示されるメッセージ
* @var string
*/
protected $recovery_msg = '::pokemonの毒は綺麗サッパリ無くなった';
}
回復メッセージの取得用メソッドも合わせて親クラスに追記しておきましょう。
状態異常(/Classes/StatusAilment.php)
<?php
// 状態異常
abstract class StatusAilment
{
/**
* インスタンス作成時に実行される処理
*
* @return void
*/
public function __construct()
{
//
}
/**
* 名称の取得
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* 状態異常にかかった際のメッセージを取得
*
* @param string $pokemon
* @return string
*/
public function getSickedMessage($pokemon)
{
return str_replace('::pokemon', $pokemon, $this->sicked_msg);
}
/**
* 状態異常にかかった際のメッセージを取得
*
* @param string $pokemon
* @return string
*/
public function getSickedAlreadyMessage($pokemon)
{
return str_replace('::pokemon', $pokemon, $this->sicked_already_msg);
}
/**
* 状態異常にかかった際のメッセージを取得
*
* @param string $pokemon
* @return string
*/
public function getTurnMessage($pokemon)
{
return str_replace('::pokemon', $pokemon, $this->turn_msg);
}
/**
* 回復時のメッセージを取得
*
* @param string $pokemon
* @return string
*/
public function getRecoveryMessage($pokemon)
{
return str_replace('::pokemon', $pokemon, $this->recovery_msg);
}
}
「やけど」のダメージ判定
現在はターン制が実装出来ていませんので、それぞれの判定は後日行います。ただし、やけどについてはダメージ計算に影響するため、こちらは現在の仕様に対して追加します。
やけど状態のポケモンが物理攻撃を行う際、ダメージが半減(0.5倍)になります。これは攻撃値が半減になるわけではなく、ダメージそのものが半減するため、ダメージ計算を行なった後に補正をかけなければいけません。なので、攻撃用トレイトのattackメソッドにこの判定を追加します。
攻撃用トレイト(/Traits/Battle/AttackTrait.php)
/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return void
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
// 技のインスタンスを取得
$move = $this->getInstance($move_class);
// 攻撃メッセージを格納
$this->setMessage($atk_pokemon->getName().'は'.$move->getName().'を使った!');
// タイプ相性チェック
$type_comp_msg = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
// 「こうかがない」の判定(命中率と威力がnullではなく、タイプ相性補正が0の場合)
if(!is_null($move->getAccuracy()) && !is_null($move->getPower()) && ($this->m === 0)){
// こうかがない
$this->setMessage($def_pokemon->getName().'には効果が無いみたいだ');
return;
}
// 命中判定
$hit = $this->checkHit($move->getAccuracy());
if(!$hit){
// 攻撃失敗
$this->setMessage('しかし'.$atk_pokemon->getName().'の攻撃は外れた!');
return;
}
// 必要ステータスの取得
$stats = $this->getStats($move->getSpecies(), $atk_pokemon, $def_pokemon);
// ダメージ計算
if($move->getSpecies() !== 'status'){
/**
* 物理,特殊技
*/
if(!is_null($move->getPower())){
// 急所判定(固定ダメージ技は判定不要)
$critical = $this->checkCritical($move->getCritical());
if($critical){
$this->setMessage('急所に当たった!');
}
}
// 乱数補正値の計算
$this->calRandNum();
// タイプ一致補正の計算
$this->calMatchType($move->getType(), $atk_pokemon->getTypes());
// ダメージ計算
$damage = $this->calDamage(
$atk_pokemon->getLevel(), # 攻撃ポケモンのレベル
$stats['a'], # 攻撃ポケモンの攻撃値
$stats['d'], # 防御ポケモンの防御値
$move->getPower(), # 技の威力
$this->m, # 補正値
);
// やけど補正
if(($move->getSpecies() === 'physical') && ($atk_pokemon->getSa() === 'SaBurn')){
// 物理且つやけど状態ならダメージを半減
$damage *= 0.5;
}
// タイプ相性のメッセージを返却
$this->setMessage($type_comp_msg);
}else{
/**
* 変化技
*/
$damage = 0;
}
// 追加効果
$move->effects($atk_pokemon, $def_pokemon);
// 追加効果のメッセージをセット
$this->setMessage($move->getMessages());
// ダメージの格納(整数)
$this->setResponse((int)$damage, 'damage');
}
物理・変化技の分岐処理内でダメージ計算終了後、物理技かどうかの確認と現在の状態異常を分岐にかけてきます。もしやけど状態且つ物理攻撃であれば、0.5の値を掛けることになり、場合によっては小数点以下が発生するので、最終的レスポンスへ格納する際にintで切り捨て処理を行なっています。
これで、やけど状態でのダメージ計算が整いました。
逃走判定
次に逃走判定を行います。今までは「にげる」を選択すれば100%の確率で成功していました、もちろんここにも確率計算が入ります。
「にげる」の成功率
それでは「にげる」を実行した際の成功率を算出する式をみてみましょう。こちらも最新のポケモンを参考に、第3世代以降の計算式かつ第5世代の確率算出を用います。
F = (A × 128 / B) + 30 × C
A : 味方のポケモンの現在のすばやさ(ランク補正等を考慮する)。
B : 相手のポケモンのすばやさ(ランク補正等は考慮しない)。
C : 逃走を試みた回数。攻撃を選択してもリセットされない。
第五世代以降 : Fを256で割った値がそのまま逃走成功率になる。
にげる(ポケモンwiki)
原則としてこちらも式に当てはめて行くだけになりますが、A(味方ポケモンのすばやさ)がランク補正込みの値となっています。現在は実数値かつ状態異常(まひ)補正の算出しか出来ていないため、後ほどランク補正の処理を実装します。
では、バトル用コントローラーを修正しましょう。
バトル用コントローラー(/Classes/Controller/BattleController.php)
<?php
require_once(__DIR__.'/../Controller.php');
require_once(__DIR__.'/../../Traits/Battle/AttackTrait.php');
// バトル用コントローラー
class BattleController extends Controller
{
use AttackTrait;
/**
* 敵ポケモン格納用
* @var object
*/
protected $enemy;
/**
* 逃走を試みた回数
* @var integer
*/
public $run = 0;
/**
* @return void
*/
public function __construct()
{
// 親コンストラクタの呼び出し
parent::__construct();
// 自ポケモンの格納
$this->myPokemon($_SESSION['pokemon']);
// 敵ポケモンの格納
if(isset($_SESSION['enemy'])){
$this->enemyPokemon($_SESSION['enemy']);
}else{
$this->enemyPokemon();
}
// ランク(バトルステータス)の引き継ぎ
if(isset($_SESSION['rank'])){
$this->pokemon
->setRank($_SESSION['rank']['pokemon']);
$this->enemy
->setRank($_SESSION['rank']['enemy']);
}
// にげるの実行回数を引き継ぎ
if(isset($_SESSION['run'])){
$this->run = $_SESSION['run'];
}
// アクションが選択された
if(isset($_POST['action'])){
// アクションメソッドの実行
$this->action(htmlspecialchars($_POST['action']), htmlspecialchars($_POST['param'] ?? null));
return;
}
}
/**
* 自ポケモンの格納
*
* @param array $pokemon
* @return void
*/
private function myPokemon($pokemon)
{
$this->pokemon = new $pokemon['class_name']($pokemon);
}
/**
* 敵ポケモンの格納
*
* @param array|null $pokemon
* @return void
*/
private function enemyPokemon($pokemon=null)
{
if(is_null($pokemon)){
$this->enemy = new Fushigidane();
$this->setMessage('野生の'.$this->enemy->getName().'が現れた!');
}else{
$this->enemy = new $pokemon['class_name']($pokemon);
}
}
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
switch ($action) {
// にげる
case 'run':
$this->run++;
if($this->checkRun()){
unset($_SESSION['enemy']);
unset($_SESSION['rank']);
unset($_SESSION['run']);
header("Location: ./home.php", true, 307);
exit;
}
$this->setMessage('逃げられない!');
break;
// たたかう
case 'fight':
$this->attack($this->pokemon, $this->enemy, $param);
$this->setResponse($this->pokemon->getRank(), $this->pokemon->getName());
$this->setResponse($this->enemy->getRank(), $this->enemy->getName());
break;
}
}
/**
* 敵ポケモン情報の取得
*
* @return object
*/
public function getEnemy()
{
return $this->enemy;
}
/**
* にげる判定
* F = (A × 128 / B) + 30 × C
* Fを256で割った値 → 逃走成功率
* @var A 味方ポケモンのすばやさ(ランク補正有り)
* @var B 相手ポケモンのすばやさ(ランク補正無し)
* @var C 逃走を試みた回数
* @return boolean
*/
private function checkRun()
{
// 味方の素早さを取得(ランク補正有り)
$a = $this->pokemon
->getStats('Speed', true);
// 相手の素早さを取得(ランク補正無し)
$b = $this->enemy
->getStats('Speed');
// 逃走を試みた回数
$c = $this->run;
// 計算式への当てはめ
$f = ($a * 128 / $b) + 30 * $c;
// 確率計算
if(round($f / 256, 2) * 100 >= mt_rand(0, 100)){
return true; # 逃走成功
}else{
return false; # 逃走失敗
}
}
}
最初に追加したプロパティを見てみましょう。
/**
* 逃走を試みた回数
* @var integer
*/
public $run = 0;
計算式のCで「逃走を試みた回数」が必要になるため、この値をどこかへ格納しておく必要があります。これはポケモンでも相手ポケモンでもなく、バトル内での数値となるためコントローラーにrunというプロパティ持たせました。逃走回数は引き継ぐ際に呼び出す必要があるため、アクセス修飾子にはpublicを用いています。
次にコンストラクタ内を見てみましょう。
// にげるの実行回数を引き継ぎ
if(isset($_SESSION['run'])){
$this->run = $_SESSION['run'];
}
ポケモンの情報と同様に、セッションを使って逃走回数の受け渡しをしています。こちらもバトル画面用ファイルでrunというキーでセッションに格納するようにしておきましょう。
次にactionメソッドのrun分岐内を見てみましょう。
// にげる
case 'run':
$this->run++;
if($this->checkRun()){
unset($_SESSION['enemy']);
unset($_SESSION['rank']);
unset($_SESSION['run']);
header("Location: ./home.php", true, 307);
exit;
}
$this->setMessage('逃げられない!');
break;
まず、run(にげる)が選択された時点で、runのプロパティを加算します。初期値は0のため、初めて「にげる」が選択された時点でCの値には1が入ります。その後、checkRunというメソッドを使って判定を行い、成功したらtrueを受け取り成功処理、失敗したらfalseを受け取り失敗メッセージを返却しています。
それでは、checkRunメソッドの処理を見ていきましょう。
/**
* にげる判定
* F = (A × 128 / B) + 30 × C
* Fを256で割った値 → 逃走成功率
* @var A 味方ポケモンのすばやさ(ランク補正有り)
* @var B 相手ポケモンのすばやさ(ランク補正無し)
* @var C 逃走を試みた回数
* @return boolean
*/
private function checkRun()
{
// 味方の素早さを取得(ランク補正有り)
$a = $this->pokemon
->getStats('Speed', true);
// 相手の素早さを取得(ランク補正無し)
$b = $this->enemy
->getStats('Speed');
// 逃走を試みた回数
$c = $this->run;
// 計算式への当てはめ
$f = ($a * 128 / $b) + 30 * $c;
// 確率計算
if(round($f / 256, 2) * 100 >= mt_rand(0, 100)){
return true; # 逃走成功
}else{
return false; # 逃走失敗
}
}
まず、$aに味方の素早さを取得します。補正値計算込みの値を取得するため、getStatsの第2引数にはtrueを指定します。次に、$bに相手の素早さを取得します。こちらは補正値計算が不要のため、第2引数は不要で実数値のみを取得します。
計算式をそのまま採用するため、変数$cを用意して逃走回数を格納しました。こちらは直接利用してもらっても構いません。
最後に、算出した確率を計算式通り$fに格納します。この状態では小数点以下細かな数値が算出されているため、確率計算時にroundを仕様して第2位までに四捨五入し、そこに100を掛けることで小数点の無いパーセントの形に直しました。あとは、他の計算処理同様にmt_rand(0, 100)を使って確率判定を行いtrueまたはfalseを返却します。
ステータス実数値(補正有り)の計算
それでは逃走確率の計算でも必要になった、ステータス実数値の計算を実装しましょう。まずはランク補正値についてです。こちらも最新世代を参考に第3世代以降の倍率を使用します。
ランク | -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 |
倍率 (概算) |
25% | 29% | 33% | 40% | 50% | 67% | 100% | 150% | 200% | 250% | 300% | 350% | 400% |
ランク補正(ポケモンwiki)
倍率の計算方法は以下の通りです。
<?php
if($rank >= 0){
$per = ($rank + 2) / 2;
}else{
$per = 2 / (2 - $rank);
}
$result = round($per, 0);
まず、ランク($rank)には対象ステータスのランク値が入ります。もし0以上であれば、分子がランク数加算されるため、
(ランク + 2)÷ 2
という式で算出ができます。
次に、マイナス補正がかかっていれば、分母にランクの自然数(マイナスを除く)が加算されるため、
2 ÷(2 ― ランク)
という式で算出ができます。
※ランクが負の数のため、自然数を加算するために減算をしています
最後に算出した結果を四捨五入することで、小数点第2位までの倍率が求められるということです。
ではこの式をステータスの実数値算出に当てはめましょう。
Get格納トレイト(/Traits/Pokemon/GetTrait.php)
/**
* ステータスの取得
*
* @param string|null
* @return array|integer
*/
public function getStats($param=null, $m=false)
{
// ポケモンのステータス(実数値)を計算して返却
foreach($this->base_stats as $key => $val){
/**
* ステータスの計算式(小数点以下は切り捨て)
* HP:(種族値×2+個体値+努力値÷4)×レベル÷100+レベル+10
* HP以外:(種族値×2+個体値+努力値÷4)×レベル÷100+5
*/
if($key === 'HP'){
$correction = $this->level + 10;
}else{
$correction = 5;
}
$stats[$key] = (int)(($val * 2 + $this->iv[$key] + $this->ev[$key] / 4) * $this->level / 100 + $correction);
}
if(is_null($param)){
// 指定がなければ全ステータスを返却
return $stats;
}
/**
* 補正値の計算
*/
$result = $stats[$param];
if($m){
// ランク補正
$rank = $this->getRank($param);
if($rank >= 0){
// +補正
$per = ($rank + 2) / 2;
}else{
// -補正
$per = 2 / (2 - $rank);
}
$result *= round($per, 2); # 補正割合は四捨五入
// 状態異常補正
if(($param === 'Speed') && $this->getSa() === 'SaParalysis'){
// すばやさ半減
$result *= 0.5;
}
}
return (int)$result; # 実数値は切り捨て
}
これで、第2引数($m)がtrueであれば、ランク補正込みのステータスを返却してくれます。これに合わせて、ダメージ計算のステータス取得も第2引数にtrueを使って補正値を算出できるようにしておきましょう。
攻撃用トレイト(/Traits/Battle/AttackTrait.php)
/**
* ステータス(攻撃値、防御値)の取得
*
* @param string $species
* @param object $atk_pokemon
* @param object $def_pokemon
* @return array
*/
private function getStats($species, $atk_pokemon, $def_pokemon)
{
// 技種類での分岐
switch ($species) {
// 物理
case 'physical':
$a = $atk_pokemon->getStats('Attack', true);
$d = $def_pokemon->getStats('Defense', true);
break;
// 特殊
case 'special':
$a = $atk_pokemon->getStats('SpAtk', true);
$d = $def_pokemon->getStats('SpDef', true);
break;
// 変化
case 'status':
// ここに変化技の処理
break;
}
// 配列にして返却
return [
'a' => $a ?? 0,
'd' => $d ?? 0,
];
}
では、「にげる」を試してみましょう。
今回ピカチュウの素早さ15(実数値)の個体を引き当ててしまった結果、高個体値のフシギダネでも87.5%(低個体値であれば95%)の確率で逃げれてしまうので、ほぼ逃げれます。ただ、稀に失敗するという状況が確認できました。どういった数値が出ているのかは、var_exportやvar_dumpを使って確認しながら計算式の間違いがないかチェックしてみると良いでしょう。
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「状態異常」と「にげる」の判定、ステータス補正値込みの計算方法をご紹介しました。
ゲームづくりに挑戦しようとしている人は、ぜひ参考にしてみてくださいね。