ダメージ固定技とは
PHPポケモンでも作成したダメージ計算機能ですが、ポケモンの技の中にはそれを必要としない技がいくつかあります。それが「固定ダメージ技」です。
ポケモンwiki(ダメージ固定技)
ステータスに依存せず、わざ自体にダメージ量が決まっているわざ。
一口にダメージ固定技と言っても効果が大きく分かれており、対戦面などでも基本的に別物として考えられる。
- 純粋にわざに定められたダメージを与えるもの
ソニックブーム、ちきゅうなげなど- 相手の現在HPから一定割合を削るもの
いかりのまえばなど- 相手から受けたダメージによって変化するもの(カウンター技)
カウンターなど- 当たる確率は低いが相手のHPを全て削るもの
一撃必殺技
以前作成した「一撃必殺技」もダメージ固定技に分類されています。
今回作成するのは、自分のレベル分のダメージを与えることができる「ちきゅうなげ」と、自分のレベルに応じた乱数ダメージを与える「サイコウェーブ」、このターン受けたダメージ量によって与えるダメージが変化する「カウンター」の3つを作成しましょう。
ちきゅうなげ
まずは「ちきゅうなげ」という技を作成します。固定ダメージ技には威力が不要のため、威力値にはnullをセットしておき、代わりに与えるダメージを返却するためのメソッド(getFixedDamage)と、ダメージ固定技を判別するためのプロパティ(fixed_damage_flg)を新しく用意しましょう。
ちきゅうなげ(/Classes/Move/SeismicToss.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// ちきゅうなげ
class SeismicToss extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'ちきゅうなげ';
/**
* 説明文
* @var string
*/
protected $description = '自分のレベル分の固定ダメージを与える';
/**
* タイプ
* @var string
*/
protected $type = 'Fighting';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'physical';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 20;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* ダメージ固定技フラグ
* @var boolean
*/
protected $fixed_damage_flg = true;
/**
* 固定ダメージ量の取得
*
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
* @return integer
*/
public function getFixedDamage($atk, $def)
{
// 攻撃ポケモンのレベル分ダメージを与える
return $atk->getLevel();
}
}
次に、親クラスにダメージ固定技フラグの取得用メソッド(getFixedDamageFlg)と、ダメージ固定技フラグのプロパティ(fixed_damage_flg)に初期値のfalseをセットしておきましょう。
技クラス(/Classes/Move.php)
<?php
$root_path = __DIR__.'/..';
require_once($root_path.'/App/Traits/InstanceTrait.php');
require_once($root_path.'/App/Traits/ResponseTrait.php');
// 技
abstract class Move
{
--省略
/**
* 固定ダメージ技フラグの取得
*
* @return boolean
*/
public function getFixedDamageFlg()
{
return $this->fixed_damage_flg;
}
これで固定ダメージ技の準備が整いました。
次にダメージ計算処理の修正です。攻撃トレイトのattackSuccessメソッド内に分岐を追加、修正しましょう。
攻撃トレイト(/App/Traits/Service/Battle/ServiceBattleTrait.php)
/**
* 攻撃判定成功時の処理
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param object $move
* @return void
*/
private function attackSuccess($atk_pokemon, $def_pokemon, $move)
{
if($move->getFixedDamageFlg()){
/**
* 固定ダメージ技
*/
$damage = $move->getFixedDamage($atk_pokemon, $def_pokemon);
}else{
/**
* 通常技
*/
// ローカル変数として補正値を用意
$m = 1;
// 必要ステータスの取得
$stats = $this->getStats($move->getSpecies(), $atk_pokemon, $def_pokemon);
// ダメージ計算(物理,特殊技)
if($move->getSpecies() !== 'status'){
// 急所判定
$critical = $this->checkCritical($move->getCritical());
if($critical){
// 補正値を乗算
$m *= $critical;
$this->setMessage('急所に当たった!');
}
// 乱数補正値の計算
$m *= $this->calRandNum();
// タイプ一致補正の計算
$this->calMatchType($move->getType(), $atk_pokemon->getTypes());
// ダメージ計算
$damage = $this->calDamage(
$atk_pokemon->getLevel(), # 攻撃ポケモンのレベル
$stats['a'], # 攻撃ポケモンの攻撃値
$stats['d'], # 防御ポケモンの防御値
$move->getPower(), # 技の威力
$this->m * $m, # 補正値(プロパティ*ローカル)
);
// やけど補正
if(($move->getSpecies() === 'physical') && ($atk_pokemon->getSa() === 'SaBurn')){
// 物理且つやけど状態ならダメージを半減
$damage *= 0.5;
}
// タイプ相性のメッセージを返却
$this->setMessage($this->type_comp_msg);
}
}
// ダメージ計算
$def_pokemon->calRemainingHp('sub', $damage ?? 0);
// 追加効果(相手にHPが残っていれば)
if($def_pokemon->getRemainingHp()){
// 追加効果
$move->effects($atk_pokemon, $def_pokemon);
// 追加効果のメッセージをセット
$this->setMessage($move->getMessages());
$move->resetMessage();
return;
}
}
固定ダメージ技のダメージ量は、各技クラスに作成したgetFixedDamageが算出してくれるため、ダメージ計算を行う必要がありません。その関係上、相手のステータス取得や補正値計算も不要です。タイプ相性の「こうかがない」に関しては従わなければなりませんが「こうかばつぐん」と「こうかいまひとつ」に関しても判定メッセージは不要です。もちろん急所に当たることもありません。
なので、攻撃成功であればまず固定ダメージかどうかの分岐を行いダメージ量を計算、その後ポケモンにダメージを与えるという順番で処理を行なっています。
「ちきゅうなげ」のgetFixedDamageメソッドでは攻撃ポケモンのレベルを返り値として返却しているので、この値がダメージ量となってそのまま計算されることになります。
以下出力結果です。
一撃必殺を除き、固定ダメージ技は大ダメージと言えるものではありません。そのため、通常のポケモンではそこまで大きなメリットを感じないかも知れませんが、攻撃力が乏しいポケモン(耐久型ポケモン)などが使えるとなればかなり心強い技の1つです。また、相手の耐久値が高い場合や、ランク補正などで通常技ではとても十分なダメージを与えられない状況においては重宝される1つです。
サイコウェーブ
次に「サイコウェーブ」を作成します。こちらも「ちきゅうなげ」の要領で作成します。
サイコウェーブ(/Classes/Move/Psywave.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// サイコウェーブ
class Psywave extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'サイコウェーブ';
/**
* 説明文
* @var string
*/
protected $description = '相手にランダムに決まった値を固定ダメージとして与える。';
/**
* タイプ
* @var string
*/
protected $type = 'Psychic';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'special';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 15;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* 固定ダメージフラグ
* @var boolean
*/
protected $fixed_damage_flg = true;
/**
* 固定ダメージ量の取得
*
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
* @return integer
*/
public function getFixedDamage($atk, $def)
{
// 攻撃ポケモンのレベル*(0.5〜1.5)倍のダメージを与える
return (int)($atk->getLevel() * (random_int(5, 15) / 10));
}
}
サイコウェーブのダメージ量は以下の通りです。
(攻撃側のレベル×0.5~攻撃側のレベル×1.5)のダメージ
固定ダメージとはいっても乱数が含まれます。なので、getFixedDamage内で乱数を作成してダメージ量を確定させます。
小数点以下が含まれる乱数はrandom_intでは作成出来ないため、5〜15の間で乱数を作成し、10で割ってからレベルを掛け、整数(切り捨て)してダメージ量を返却しています。
初代技であれば、「ソニックブーム」や「りゅうのいかり」、「ナイトヘッド」「いかりのまえば」が同じ仕組みを使って作成することができます。ダメージ量が決まっている技はそのまま数値を返却、それ以外も攻撃ポケモンまたは防御ポケモンのインスタンスを引数で受け取っていれば算出が可能なものです。
カウンター
ダメージ固定技の中でも「ちきゅうなげ」などとは同じ仕組みだけでは実装できないものがあります。それが「カウンター」という技です。
(使用したターンの最後に受けた物理技のダメージ×2)のダメージ
受けたダメージの格納
カウンターは優先度マイナス5という技のため、もし相手も同じ優先度の技を使用していない限りは後手になります。更に、ダメージ量の計算が「使用ターンに自身が受けた物理ダメージの2倍」となるので、技によって受けた最後のダメージ量を格納しておかなければなりません。
なので、ポケモンに対して新しくプロパティを用意することで対応しましょう。
ポケモンクラス(/Classes/Pokemon.php)
// ポケモン
abstract class Pokemon
{
--省略
/**
* このターンに受けた攻撃によるダメージ
* @var array
*/
protected $turn_damage = [
'physical' => 0,
'special' => 0,
];
ダメージは物理・特殊の2種類あるため、連想配列で用意します。プロパティの作成に合わせて取得(getTurnDamage)、格納(setTurnDamage)、リセット(resetTurnDamage)を用意します。
ポケモン用Get格納トレイト(/App/Traits/Class/Pokemon/ClassPokemonGetTrait.php)
/**
* このターンに受けたダメージの取得
* @param string (physical|special)
* @return mixed
*/
public function getTurnDamage($param='')
{
if(empty($param)){
// 配列で全返却
return $this->turn_damage;
}else{
// 指定ダメージを取得
return $this->turn_damage[$param] ?? 0;
}
}
ポケモン用Set格納トレイト(/App/Traits/Class/Pokemon/ClassPokemonSetTrait.php)
/**
* このターン受けたダメージ量の格納
* @param string $param
* @param integer $damage
* @return void
*/
public function setTurnDamage($param, int $damage)
{
$this->turn_damage[$param] = $damage;
}
ポケモン用Reset格納トレイト(/App/Traits/Class/Pokemon/ClassPokemonResetTrait.php)
<?php
trait ClassPokemonResetTrait
{
/**
* このターン受けたダメージ量をリセットする
* @return void
*/
public function resetTurnDamage()
{
$this->turn_damage = [
'physical' => 0,
'special' => 0,
];
}
}
※リセットトレイトは新しく作成しています
これで準備が整いました。引き継ぎ対象としていないため、前回のダメージがそのまま残り続けることはありませんが、ループで行う自動ターン処理があるため、念には念を入れてサービス開始時にまずリセットを実行しておきましょう。
たたかうサービス(/App/Services/Battle/FightService.php)
/**
* @return void
*/
public function execute()
{
// ターンダメージのリセット
$this->pokemon
->resetTurnDamage();
$this->enemy
->resetTurnDamage();
次にダメージの格納処理です。こちらはダメージ計算終了後にメソッドを呼び出して、算出したダメージをダメージ計算前に技種別に合わせて格納します。
攻撃トレイト(/App/Traits/Service/Battle/ServiceBattleAttackTrait.php)
/**
* 攻撃判定成功時の処理
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param object $move
* @return void
*/
private function attackSuccess($atk_pokemon, $def_pokemon, $move)
{
if($move->getFixedDamageFlg()){
/**
* 固定ダメージ技
*/
$damage = $move->getFixedDamage($atk_pokemon, $def_pokemon);
}else{
/**
* 通常技
*/
// ローカル変数として補正値を用意
$m = 1;
// 必要ステータスの取得
$stats = $this->getStats($move->getSpecies(), $atk_pokemon, $def_pokemon);
// ダメージ計算(物理,特殊技)
if($move->getSpecies() !== 'status'){
// 急所判定
$critical = $this->checkCritical($move->getCritical());
if($critical){
// 補正値を乗算
$m *= $critical;
$this->setMessage('急所に当たった!');
}
// 乱数補正値の計算
$m *= $this->calRandNum();
// タイプ一致補正の計算
$this->calMatchType($move->getType(), $atk_pokemon->getTypes());
// ダメージ計算
$damage = $this->calDamage(
$atk_pokemon->getLevel(), # 攻撃ポケモンのレベル
$stats['a'], # 攻撃ポケモンの攻撃値
$stats['d'], # 防御ポケモンの防御値
$move->getPower(), # 技の威力
$this->m * $m, # 補正値(プロパティ*ローカル)
);
// やけど補正
if(($move->getSpecies() === 'physical') && ($atk_pokemon->getSa() === 'SaBurn')){
// 物理且つやけど状態ならダメージを半減
$damage *= 0.5;
}
// タイプ相性のメッセージを返却
$this->setMessage($this->type_comp_msg);
}
}
// このターン受けるダメージをポケモンに格納
$def_pokemon->setTurnDamage($move->getSpecies(), $damage ?? 0);
// ダメージ計算
$def_pokemon->calRemainingHp('sub', $damage ?? 0);
これでダメージ量の補完処理が作成できました。
では、カウンターの技クラスを作成しましょう。
カウンター(/Classes/Move/Counter.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// カウンター
class Counter extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'カウンター';
/**
* 説明文
* @var string
*/
protected $description = '相手から受けた物理攻撃のダメージを2倍にして与える。';
/**
* タイプ
* @var string
*/
protected $type = 'Fighting';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'physical';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 20;
/**
* 優先度
* @var integer
*/
protected $priority = -5;
/**
* 固定ダメージフラグ
* @var boolean
*/
protected $fixed_damage_flg = true;
/**
* 固定ダメージ量の取得
*
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
* @return integer
*/
public function getFixedDamage($atk, $def)
{
// 攻撃ポケモンのレベル分ダメージを与える
return $atk->getTurnDamage('physical') * 2;
}
}
これでカウンターで与える固定ダメージ量が計算できるようになりました。
ただ、カウンターは相手からこのターン物理ダメージを受けていなければ「攻撃失敗」という処理が必要です。なので、こちらは命中判定処理に追記しましょう。
攻撃トレイト(/App/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;
}
// カウンターの失敗判定
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;
}
カウンターは特別な技のため、技クラスで個別の判定を加えました。初代以外ではミラーコートやメタルバーストなどがありますが、もしすべてを実装したとしても数が知れているので、一旦は個別処理で対応します。
では、実際にカウンターを使用してみましょう。
カウンター成功
カウンター失敗
攻撃が外れた際には失敗となりました。これで正常に判定がされているということがわかりますね。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「ダメージ固定技」について「ちきゅうなげ」「サイコウェーブ」「カウンター」の作成方法をご紹介しました。
プログラミング学習に取り組んでいる方や、ゲームづくりに興味がある人は、ぜひ参考にしてみてくださいね。