経験値の分配とは
大型アップデート後、最初の追加機能は「経験値の分配」についてです。
ポケモンの入れ替えができるようになりましたが、経験値をもらえるのは現在フィニッシャーのみとなっており、レベルの低いポケモンを育てるのが困難な状態です。なので、ゲームバランスを整えるためにも、まず経験値周りの処理から見直しをしていきましょう。
経験値の計算式
分配の計算式についてですが、世代によって微妙に異なっています。最新の第8世代ではポケモン全てに経験値が割り振られたり、世代によっては選出されたポケモンそれぞれに計算式が適用される場合もあります。
ですが、この点においては旧世代を参考にしながらオリジナルの計算式として、基本となる経験値量を選出されたポケモン数に分配、その後レベル補正をかけるという仕組みで実装していきます。
まずはPHPポケモンで現在使用している経験値の算出式を見てみましょう。
経験値の計算式(旧)
EXP × LM^2.5 + 1
- EXP 倒されたポケモンのレベル × 倒されたポケモンの基礎経験値 ÷ 5
- LM レベル補正 (2L + 10) / (L + Lp + 10)
- L 倒されたポケモンのレベル
- Lp 倒したポケモンのレベル
レベル補正が掛かる前のEXPが分配される基本の経験値となります。これを選出されたポケモン数で割るため、分配式は以下の通りです。
経験値の計算式(新)
EXP / count × LM^2.5 + 1
- count 戦闘に参加したポケモンの数
これで、均等に経験値を分け合い且つ、レベル補正はそれぞれに依存させることができます。
選出ポケモン情報の保管
選出されたポケモンの記録は、バトル状態クラス(battle_state)を使って管理します。保管用のプロパティとしてfought_ordersを初期値配列で作成、ここに選出されたポケモン番号を格納していきます。
バトル状態ポケモン関係トレイト(/App/Traits/Class/BattleState/ClassBattleStatePokemonTrait.php)
/**
* 戦闘に参加しているポケモン番号の格納(private)
* @param order:integer
* @return void
*/
private function setOrder(int $order): void
{
// 番号をプロパティへ格納
$this->order = $order;
// 戦闘に参加したポケモンリストに格納
if(!in_array($order, $this->fought_orders, true)){
$this->fought_orders[] = $order;
}
}
選出されたポケモンが増えるタイミングは、オーダーセット時です。なので、オーダーをセットする処理をメソッド管理にして、このタイミングに選出リストへ加えていきます。
同じポケモンが再度選出される可能性も考慮して、in_arrayで重複回避をしています。
選出されたとしても、そのポケモンが戦闘不能になっていれば経験値は貰えません。ですが、アイテムなどを使ってひんし状態が解除されれば経験値をもらうことができます。
なので、瀕死状態の確認はポケモン番号を取得する際にフィルターがけすることで判定します。
/**
* 経験値を貰える権利があるポケモン番号の取得
* @return array
*/
public function getEntitledExpOrders(): array
{
/**
* 現在戦闘中のポケモンを先頭にする
*/
// 現在戦闘中のポケモン番号を削除(次のarray_unshiftが破壊的な関数のため変数へ格納)
$fought_orders = array_diff($this->fought_orders, [$this->order]);
// 先頭に現在のポケモン番号を追加
array_unshift($fought_orders, $this->order);
// 戦闘可能状態でフィルターにかけて返却
return array_filter(
$fought_orders,
function($order){
return player()->getPartner($order)->isFight();
}
);
}
経験値取得の順番ですが、フィニッシャーが最初に来るように、一度array_diffを使って現在のポケモン番号を削除、array_unshiftで再度先頭へ追加しています。
array_unshift(PHP.net)
※フィニッシャーが最後にならなくても不具合自体は発生しませんが、本作同様の手順で処理が行われるようにしています。
また、経験値バーの描画処理などは、現在のポケモン番号とフィニッシャーが一致していたときのみ生成されるように分岐させました。
それでは、実際の動きを進化処理まで含めて見てみましょう。
進化処理の作成段階では複数を想定して組んでいたため、問題なく処理されることが確認できました。
以上で経験値の分配機能は完成です。
技習得ポケモン複数発生時の対策
今まではポケモンが1匹のみを想定していたため、同時に技習得が発生した際に「バトル中のポケモン」を対象に処理を行なっていました。ですが、経験値の分配により同時に技習得が発生する可能性が出てきたため、どのポケモンの技を書き換えるかを判定する必要が出てきました。
例(現段階の処理)
ピカチュウ(フィニッシャー)
フシギダネ(選出:つるのムチを覚えたい)
→ ピカチュウが「つるのムチ」を覚えてしまう
これを回避するためにも、技習得のレスポンスに対してポケモンのIDを持たせ、習得時のポケモン選出をこのIDを使って検索したポケモンのオブジェクトに対して行います。
ポケモンクラスチェック用トレイト(/App/Traits/Class/Pokemon/ClassPokemonCheckTrait.php)
// checkLevelMoveメソッド内
/**
* 技選択用モーダルの返却
*/
// メッセージIDを生成
$msg_id = response()->issueMsgId();
// レベルアップメッセージ
response()->setMessage($this->getNickName().'は'.$move->getName().'を覚えたい');
response()->setMessage('しかし技を4つ覚えるので精一杯だ');
response()->setMessage($move->getName().'の代わりに他の技を忘れさせますか?', $msg_id);
// レスポンスデータをセット
response()->setResponse([
'toggle' => 'modal',
'target' => '#'.$msg_id.'-modal',
'move' => $move_class,
'pokemon_id' => $this->id #追加したポケモン判別用ID
], $msg_id);
// モーダル用のレスポンスをセット
response()->setModal([
'id' => $msg_id,
'modal' => 'acquire-move',
'new_move' => $move,
'pokemon_id' => $this->id
]);
// 諦めメッセージを事前に用意しておく
response()->setMessage($this->getNickName().'は'.$move->getName().'を覚えるのを諦めた');
技習得サービス(/App/Services/Battle/LearnMoveService.php)
/**
* 技の置き換え
* @return void
*/
private function replaceMove()
{
// 技を習得する対象のポケモンのIDを旧レスポンスから取得(交代したポケモンを想定)
$pokemon = player()->getPartner(
$this->before_responses[request('param.id')]['pokemon_id'], 'id'
);
// 忘れる技を取得
$forget_move = $pokemon
->getMove(request('param.forget'));
// 覚えさせる技を旧レスポンスから取得
$new_move = new $this->before_responses[request('param.id')]['move'];
// 技を覚えさせる
$pokemon
->setMove($new_move, request('param.forget'));
// メッセージの返却
response()->setMessage('1 2の ……ポカン!');
response()->setMessage($pokemon->getNickname().'は、'.$forget_move->getName().'の使い方をキレイに忘れた!そして......');
response()->setMessage($pokemon->getNickname().'は新しく、'.$new_move->getName().'を覚えた!');
}
ユーザー入力によるバグ回避
ブラウザを使ったゲームにおいて気をつけて置かなければならないのが「ユーザー入力」による値の想定です。例えば、デベロッパーツールから値を直接変更すれば、インターフェースで制限を設けていたとしても「予期せぬ値」がPOSTされてきます。
もちろん、無理な操作でデータが消失することに関してはプレイヤー側の責任として問題ありませんが、正常に処理が通ってしまうことが一番の問題となります。
例えば、今回のポケモンを判別するための値をinput hiddenで用意したとしましょう。これをノードの直接変更で別のポケモンが指定されてしまえば、本来覚えるはずのない技を覚えさせることが出来てしまいます。こういったバグを回避するためにも、できるだけPOSTする値にはクラス名や対象を特定するようなものは避けています。
オーダーを使った処理をしていれば、配列自体が変更されることがないため、最低でも選択肢が増えるということはありませんね。
様々なケースを想定していますが、場合によっては回避する手段があるかも知れません。此の辺りは見つけ次第対応予定ですので、ぜひ気づいた方はお問い合わせよりご連絡ください。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「経験値の分配」についてご紹介しました。
WEBアプリケーション開発に興味がある方や、現在プログラミング学習に取り組んでいる方は、ぜひ参考にしてみてくださいね。