フィールド効果技とは
ポケモンの技の中には、ポケモンに対して状態変化や異常を与えるもの以外に、フィールド自体に効果を持たせるものがいくつかあります。PHPポケモンでは未実装ですが、そういったフィールド効果技はポケモンを交代したとしても場に効果が残り続けます。
場の状態(ポケモンwiki)
場の状態に似たものとして、状態異常と状態変化がある。これら2つはポケモン1匹について関係する状態だが、場の状態はバトルフィールドについて関係する状態である。したがって、ポケモンを交代させても、場の状態の持続・回復(解除)には基本的に関与しない。
対象範囲として、味方・相手いずれか片方の場の状態と、全体の場の状態の2つがある。
ポケモンにとって有利なものと、不利なものそれぞれがある。また、一部を除いて異なる場の状態を重ねがけすることができる。
初代ポケモンでは自分の場に対して影響を与える技しか存在しません。第2世代からは天気技と、相手の場に影響を与える技(まきびし等)が実装されました。
フィールド状態のセット
まずはフィールド対象技を使用した際に格納するためのプロパティをコントローラーに用意します。処理自体はサービスで行うため、runなどと同じようにプロパティを引き継ぐ形式で実装しましょう。
バトルコントローラー(/App/Controllers/Battle/BattleController.php)
<?php
$root_path = __DIR__.'/../../..';
require_once($root_path.'/App/Controllers/Controller.php');
// サービス
require_once($root_path.'/App/Services/Battle/StartService.php');
require_once($root_path.'/App/Services/Battle/RunService.php');
require_once($root_path.'/App/Services/Battle/FightService.php');
// トレイト
require_once($root_path.'/App/Traits/Common/CommonFieldTrait.php');
require_once($root_path.'/App/Traits/Controller/BattleControllerTrait.php');
// バトル用コントローラー
class BattleController extends Controller
{
use CommonFieldTrait;
use BattleControllerTrait;
/**
* 敵ポケモン格納用
* @var object
*/
protected $enemy;
/**
* 逃走を試みた回数
* @var integer
*/
public $run = 0;
/**
* フィールド効果
* @var integer
*/
protected $field = [
'friend' => [],
'enemy' => [],
];
/**
* ひんし状態の格納
* @var array
*/
protected $fainting = [
'friend' => false,
'enemy' => false,
];
/**
* 前ターンのポケモンの状態
* @var array
*/
protected $before = [
'friend' => null,
'enemy' => null,
];
/**
* @return void
*/
public function __construct()
{
// 親コンストラクタの呼び出し
parent::__construct();
// 引き継ぎ
$this->takeOver();
// 分岐処理
$this->branch();
// 次のターンへの分岐(ループ処理)
while($this->nextTurn());
// 親デストラクタの呼び出し
parent::__destruct();
}
/**
* 引き継ぎ処理
* @return void
*/
private function takeOver()
{
// にげるの実行回数を引き継ぎ
if(isset($_SESSION['__data']['run'])){
$this->run = $_SESSION['__data']['run'];
}
// フィールド状態を引き継ぎ
if(isset($_SESSION['__data']['field'])){
$this->field = $_SESSION['__data']['field'];
}
// ポケモンの引き継ぎ
$this->takeOverPokemon($_SESSION['__data']['pokemon']);
// 敵ポケモンの引き継ぎ
$this->takeOverEnemy($_SESSION['__data']['enemy'] ?? '');
}
/**
* アクションに合わせた分岐
* @return void
*/
private function branch()
{
try {
// アクション分岐
switch ($this->request('action')) {
/******************************************
* 開始
*/
case 'battle':
// サービス実行
$service = new StartService($this->pokemon);
$service->execute();
// 実行結果
$this->enemy = $service->getResponse('enemy');
// 前ターンの状態をプロパティに格納
$this->before['enemy'] = clone $this->enemy;
$this->setMessage($service->getMessages());
$this->setResponse($service->getResponses());
break;
/******************************************
* たたかう
*/
case 'fight':
// サービス実行
$service = new FightService(
$this->pokemon,
$this->enemy,
$this->request('param'),
$this->field
);
$service->execute();
// 実行結果
$this->fainting = $service->getResponse('fainting');
$this->field = $service->getResponse('field');
$this->setMessage($service->getMessages());
$this->setResponse($service->getResponses());
break;
/******************************************
* にげる
*/
case 'run':
// 回数をプラス
$this->run++;
// サービス実行
$service = new RunService(
$this->pokemon,
$this->enemy,
$this->run,
$this->field
);
$service->execute();
// 実行結果
if(!$service->getResponse('result')){
// 失敗
$this->fainting = $service->getResponse('fainting');
$this->field = $service->getResponse('field');
}
$this->setMessage($service->getMessages());
$this->setResponse($service->getResponses());
break;
/******************************************
* バトル終了
*/
case 'end':
$this->battleEnd();
break;
/******************************************
* アクション未選択 or 実装されていないアクション
*/
default:
// もしどちらかが戦闘不能状態であればバトルを強制終了
if(empty($this->before_remaining_hp['friend']) || empty($this->before_remaining_hp['enemy'])){
$this->battleEnd();
}
break;
}
} catch (\Exception $e) {
// 初期画面へ移管
$_SESSION['__route'] = 'initial';
header("Location: ./", true, 307);
exit;
}
}
/**
* バトル終了メソッド
*
* @return boolean
*/
private function battleEnd()
{
// 破棄
unset(
$_SESSION['__data']['enemy'],
$_SESSION['__data']['rank'],
$_SESSION['__data']['sc'],
$_SESSION['__data']['run'],
$_SESSION['__data']['field']
);
$_SESSION['__route'] = 'home';
header("Location: ./", true, 307);
exit;
}
/**
* 次のターンへの判定処理
*
* @return boolean
*/
private function nextTurn()
{
// ひんしポケモンがでた場合の処理
if($this->fainting['enemy'] || $this->fainting['friend']){
$this->judgment();
return false;
}
// チャージ中または反動有りなら再度アクション実行
if($this->chargeNow() || $this->pokemon->checkSc('ScRecoil')){
$this->branch();
return true;
}else{
$this->setMessage('行動を選択してください');
return false;
}
}
}
フィールド処理はコントローラーとサービスの両方で呼び出す可能性があるため、Commonディレクトリに対して共通トレイトを用意しました。
フィールドトレイト(/App/Traits/Common/CommonFieldTrait.php)
<?php
// フィールド関係のトレイト
trait CommonFieldTrait
{
/**
* フィールド状態の確認
*
* @param string $target
* @param Field:object $field
* @return boolean
*/
protected function checkField(string $target, object $field)
{
if(isset($this->field[$target][get_class($field)])){
return true;
}else{
return false;
}
}
/**
* フィールド状態の取得
*
* @return boolean
*/
public function getField()
{
return $this->field;
}
/**
* フィールドのセット
*
* @param string $target
* @param Field:object $field
* @param integer $turn
* @return void
*/
protected function setField(string $target, object $field, int $turn)
{
if($this->checkField($target, $field)){
// 既にセットされている
$this->setMessage($field->getAlreadyMessage($target));
}else{
// フィールドをセット
$this->field[$target][get_class($field)] = $turn;
$this->setMessage($field->getSetMessage($target));
}
}
/**
* フィールド状態の解除
*
* @param string $target
* @param Field:object $field
* @return boolean
*/
protected function releaseField(string $target, object $field)
{
if($this->checkField($target, $field)){
// 解除
unset($this->field[$target][get_class($field)]);
// 解除メッセージをセット
$this->setMessage($field->getReleaseMessage($target));
}
}
/**
* ターンカウントをすすめる(状態変化)
*
* @return void
*/
protected function goFieldTurn()
{
$targets = ['friend', 'enemy'];
foreach($targets as $target){
// 全ターゲットのフィールド状態を解除
foreach($this->field[$target] as $field => &$turn){
$turn--;
if($turn <= 0){
// 残ターンが0ターン以下になれば解除
$this->releaseField($target, new $field);
}
}
}
}
}
フィールドも状態変化や状態異常と同様に親クラス・子クラスで管理をしていきます。それを想定してチェック(checkField)、取得(getField)、セット(setField)、解除(releaseField)、ターン進行(goFieldTurn)の5つのメソッドを用意しました。
チェックは汎用的に使うメソッドです。基本的にgetを使いながら処理を進行させていくわけではありません。こちらはあくまで画面移管での引き継ぎに使用するために用意したメソッドなので、そのまま中身を返却しています。
セットメソッドは状態変化などと同様です。一番親となるキーはポケモンの立場(position)、次のキーでフィールドクラスを判別し、値には残りターン数をセットしています。
ターンの進行
次にターンの進行処理についてです。こちらは行動がすべて終了した時点でカウントを進めます。現在は交代ポケモンがいませんので、どちらかが戦闘不能になれば解除処理は不要ですが、もし味方または相手に交代ポケモンがいればターンカウントを進める必要があるので、サービスの最終段階でgoFieldTrunのメソッドを呼び出します。
たたかうのサービス(/App/Services/Battle/FightService.php)
/**
* @return void
*/
public function execute()
{
// ターンダメージのリセット
$this->pokemon
->resetTurnDamage();
$this->enemy
->resetTurnDamage();
// 技取得
$p_move = $this->selectMove();
$e_move = $this->selectEnemyMove();
// 行動順の取得
$orders = $this->orderMove(
[$this->pokemon, $this->enemy, $p_move],
[$this->enemy, $this->pokemon, $e_move],
);
// 攻撃処理
if($this->actionAttack($orders)){
// 行動後の状態異常・変化をチェック
$this->afterCheck();
}
// フィールドのカウントを進める
$this->goFieldTurn();
// 指定したプロパティを返却
$this->exportProperty('fainting', 'field');
}
※にげるの場合でもターンカウントを進める必要があるので、同様に進行処理を記述しておきましょう
解除のタイミングについてはターンカウント進行後です。残ターン数が0になっていればunsetで取り除きます。ターン進行が行動後のため、もし後攻でフィールド効果技を使用すれば実質4ターンのみしか効果が発揮されないことになります。
しろいきり
それでは、フィールド効果技の1つ目として「しろいきり」を実装しましょう。マルチバトルでは重宝されるようですが、1対1の場合は採用率が少なく、正直なところ出力メッセージも曖昧な部分がありますが、wikiを参考にしながら作成していきます。
しろいきり(ポケモンwiki)
しろいきり:技(Classes/Move/MoveMist.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// しろいきり
class MoveMist extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'しろいきり';
/**
* 説明文
* @var string
*/
protected $description = '5ターンの間、場をしろいきり状態にして能力を下げられなくする。';
/**
* タイプ
* @var string
*/
protected $type = 'TypeIce';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'status';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = null;
/**
* 使用回数
* @var integer
*/
protected $pp = 30;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* フィールド効果
*
* @return array
*/
public function field()
{
return [
'class' => 'FieldMist',
'turn' => 5,
];
}
}
こちらはフィールドに対して効果を発生させなければいけないので、effects内では処理ができません。なので、新しくfieldというメソッドを作成してフィールドクラスとターン数を配列で返却しましょう。
他の技クラスでもfieldメソッドを呼び出してエラーが出てしまわないよう、親クラス(Move.php)では空配列を返却するfieldメソッドを用意しておいてください。
次に、フィールド用のクラスを作成します。
フィールドクラス(/Classes/Field.php)
<?php
// フィールド状態
abstract class Field
{
// プロパティの初期値
protected $set_msg = '';
protected $already_msg = '';
protected $release_msg = '';
protected $failed_msg = '';
/**
* インスタンス作成時に実行される処理
*
* @return void
*/
public function __construct()
{
//
}
/**
* フィールドセット時のメッセージ
*
* @param string $target
* @return string
*/
public function getSetMessage($target)
{
$prefix = '味方は';
if($target === 'enemy'){
$prefix = '相手は';
}
return ($prefix ?? '').$this->set_msg;
}
/**
* 既にフィールドがセットされている時のメッセージ
*
* @param string $target
* @return string
*/
public function getAlreadyMessage($target)
{
$prefix = '味方は';
if($target === 'enemy'){
$prefix = '相手は';
}
return ($prefix ?? '').$this->already_msg;
}
/**
* フィールド解除時のメッセージ
*
* @param string $target
* @return string
*/
public function getReleaseMessage($target)
{
$prefix = '味方の';
if($target === 'enemy'){
$prefix = '相手の';
}
return ($prefix ?? '').$this->release_msg;
}
/**
* 状態異常にかかった際のメッセージを取得
*
* @param string $pokemon
* @return string
*/
public function getFailedMessage($pokemon)
{
return str_replace('::pokemon', $pokemon, $this->failed_msg);
}
}
しろいきり:フィールド(/Classes/Field/FieldMist.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Field.php');
// しろいきり
class FieldMist extends Field
{
/**
* 正式名称
* @var string
*/
protected $name = 'しろいきり';
/**
* フィールドセット時のメッセージ
* @var string
*/
protected $set_msg = 'しろいきりに包まれた';
/**
* 既にフィールドセットされている状態のメッセージ
* @var string
*/
protected $already_msg = '既にしろいきりに包まれている';
/**
* フィールド解除時のメッセージ
* @var string
*/
protected $release_msg = 'しろいきりが晴れた';
/**
* デバフ無効化時のメッセージ
* @var string
*/
protected $failed_msg = '::pokemonは、しろいきりに守られている';
}
新しいクラスのディレクトリが作成されたので、オートローダーに検索対象となるディレクトリ(Field)を追加しておきましょう。
デバフの分岐
しろいきりの効果は、能力(ランク)を下げられなくするという効果です。ですが、現在の仕様ではその制御を一括でする方法がなく、effectsメソッドを呼び出す際にフィールドを受け取り、しろいきり状態でなければ処理を行うといった記述が必要になります。
その場合、すべての技で分岐を作る必要があり保守性が悪くなってしまうため、能力を下げる効果(デバフ)をeffectsではなく、新しくdebuffというメソッドで実行させましょう。
なきごえ:技(/Classes/Move/MoveGrowl.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// なきごえ
class MoveGrowl extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'なきごえ';
/**
* 説明文
* @var string
*/
protected $description = '相手のこうげきを一段階下げる。';
/**
* タイプ
* @var string
*/
protected $type = 'TypeNormal';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'status';
/**
* 威力
* @var integer
*/
protected $power = null;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 40;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* 能力下降確定技フラグ
* @var boolean
*/
protected $confirm_debuff_flg = true;
/**
* 能力下降効果
*
* @param array $args
* @return void
*/
public function debuff(...$args)
{
/**
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
*/
list($atk, $def) = $args;
// 相手の攻撃ランクを1段階下げる
$msg = $def->subRank('Attack', 1);
// メッセージをセット
$this->setMessage($msg);
}
}
今まではeffectsメソッドを使って呼び出していましたが、こちらをdebuffというメソッドに変更しています。また、この技が確定効果としてデバフが備わっている場合は「しろいきりに守られている」ということをメッセージで返却する必要があるので、判別用のフラグプロパティ($confirm_debuff_flg)を設定しておきましょう。
「あわ」や「サイコキネシス」など、技の追加効果として確率でデバフが発生する技では、メソッドの変更だけを行いフラグは不要です。
あわ:技(/Classes/Move/MoveBubble.php)
<?php
$root_path = __DIR__.'/../..';
require_once($root_path.'/Classes/Move.php');
// あわ
class MoveBubble extends Move
{
/**
* 正式名称
* @var string
*/
protected $name = 'あわ';
/**
* 説明文
* @var string
*/
protected $description = '10%の確率ですばやさを1段階下げる。';
/**
* タイプ
* @var string
*/
protected $type = 'TypeWater';
/**
* 分類
* @var string(physical:物理|special:特殊|status:変化)
*/
protected $species = 'special';
/**
* 威力
* @var integer
*/
protected $power = 40;
/**
* 命中率
* @var integer
*/
protected $accuracy = 100;
/**
* 使用回数
* @var integer
*/
protected $pp = 30;
/**
* 優先度
* @var integer
*/
protected $priority = 0;
/**
* 能力下降効果
*
* @param array $args
* @return void
*/
public function debuff(...$args)
{
// 10%の確率
if(10 < random_int(1, 100)){
// random_intで11以上が生成されたら失敗
return;
}
/**
* @param Pokemon:object $atk 攻撃ポケモン
* @param Pokemon:object $def 防御ポケモン
*/
list($atk, $def) = $args;
// 相手の素早さランクを1段階下げる
$msg = $def->subRank('Speed', 1);
// メッセージをセット
$this->setMessage($msg);
}
}
次にdebuffメソッドの呼び出しについてです。こちらは攻撃用トレイトのeffectsメソッドの後に記述しましょう。
攻撃用トレイト(/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)
{
--省略
// 追加効果(相手にHPが残っていれば)
if($def_pokemon->getRemainingHp()){
// 追加効果
$move->effects($atk_pokemon, $def_pokemon);
// 能力下降効果
$field_mist = new FieldMist;
if($this->checkField($def_pokemon->getPosition(), $field_mist)){
// 能力下降確定技であれば失敗メッセージを出力
if($move->getConfirmDebuffFlg()){
$this->setMessage($field_mist->getFailedMessage($def_pokemon->getPrefixName()));
}
}else{
$move->debuff($atk_pokemon, $def_pokemon);
}
// メッセージとレスポンスを格納
$this->setMessage($move->getMessages());
$this->setResponse($move->getResponses());
// フィールド効果
if($move->field()){
$field = $move->field();
// フィールドをセット
$this->setField($atk_pokemon->getPosition(), new $field['class'], $field['turn']);
}
// メッセージとレスポンスをリセット
$move->resetMessage();
$move->resetResponse();
// いかり判定
if($def_pokemon->checkSc('ScRage') && !empty($damage ?? 0)){
$rage = new ScRage;
// いかり発動メッセージをセット
$this->setMessage($rage->getActiveMessage($def_pokemon->getPrefixName()));
// こうげきランクを1段階上昇
$msg = $def_pokemon->addRank('Attack', 1);
$this->setMessage($msg);
}
return;
}
}
getConfirmDebuffFlgという判別フラグ取得用のメソッドについても、技の親クラスに追記しています。こちらでは$confirm_debuff_flgの値を返却しているだけです。
もしデバフ確定技であれば、しろいきりによる失敗メッセージを返却、そうでなければ処理そのものをスルーします。
しろいきり状態でなければ、そのままdebuffメソッドを呼び出し、effectsで溜まったメッセージと一緒に取り出し、格納しています。
それでは、しろいきりの効果を見てみましょう。
相手のデバフ技に対して、しろいきりで守ることができました。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「フィールド効果技(しろいきり)」の実装方法を紹介しました。
ゲーム開発やプログラミングに興味がある方は、ぜひ参考にしてみてくださいね。