![ゲームバランス調整編 DateTimeクラスの活用 PHPポケモン 101](https://s-yqual.com/yqual/wp-content/uploads/2020/12/20201217.jpg)
ゲームバランスの調整
まだまだ開発途中のPHPポケモンはゲームバランスの調整がほとんどされていません。現段階では機能も揃っていない関係上、完全なゲームバランスを求めて行くことにはあまり意味がありませんが、今後調整するための役割として機能自体は作成しておこうというのが今回の目的です。
トレーナー戦の上限回数
本番環境にはまだ未反映ですが、トレーナー戦が今回調整を加える対象となります。1日に何度も同じトレーナーと戦えてしまうと、お金を短時間に荒稼ぎしたり、経験値を狙って取得することが出来てしまいます。なので、1日に戦える回数の上限を設けることで調整していきましょう。
トレーナー情報の保管
まずはプレイヤークラスに対して、戦ったトレーナー情報を保管、操作するためのメソッドを用意しましょう。いくつかあるので、専用トレイトを用意します。
プレイヤー用トレーナー操作トレイト(/App/Traits/Class/Player/ClassPlayerTrainerTrait.php)
<?php
/**==================================================================
* トレーナー情報(勝利したトレーナー情報を格納)
==================================================================**/
trait ClassPlayerTrainerTrait
{
/**
* トレーナー
* @var array
*/
protected $trainers = [];
/**
* トレーナーと戦える回数
* @var integer
*/
protected $trainer_times = 5;
/**
* 対象トレーナーと戦ったかどうかの確認
* @param category:string
* @param id:string
* @return boolean
*/
public function isTrainer(string $category, string $id): bool
{
return in_array($id, $this->trainers[$category], true);
}
/**
* 対象トレーナーと戦えるかの確認
* @param category:string
* @return boolean
*/
public function isFightTrainer(string $category): bool
{
return $this->getTrainerCount($category) < $this->trainer_times;
}
/**
* 戦ったトレーナー名一覧を取得
* @param category:string
* @return array
*/
public function getTrainers(string $category): array
{
return $this->trainers[$category] ?? [];
}
/**
* 戦った全トレーナー名一覧を取得
* @param category:string
* @return array
*/
public function getTrainersAll(): array
{
return $this->trainers ?? [];
}
/**
* 戦ったトレーナー数を取得
* @param category:string
* @return integer
*/
public function getTrainerCount(string $category): int
{
return count($this->trainers[$category] ?? []);
}
/**
* 対象トレーナーと戦える残り回数を取得
* @param category:string
* @return integer
*/
public function getRemainingTrainerCount(string $category): int
{
return $this->trainer_times - $this->getTrainerCount($category);
}
/**
* 戦ったトレーナー情報の格納
* @param trainer:object::Trainer
* @return void
*/
public function setTrainer(Trainer $trainer): void
{
$this->trainers[$trainer->getCategory()][] = $trainer->getId();
}
}
トレーナー情報はカテゴリごとに配列で保管、戦える上限回数をプロパティで設定しました。上限回数を定数・静的変数ではない理由の1つとして、プレイヤーレベルに合わせてこの回数を変更したり、ある条件を満たすことで回数を増やすなどの変動があることでゲーム性がより広がると考えたからです。そういった機能を想定しないのであれば、上限回数は定数または静的変数で用意するのがメモリ消費を抑えられるため良いでしょう。
トレーナー情報を判別するために、トレーナーIDという値を1つ追加しました。これは、戦ったトレーナー情報配列が格納されたファイル名(拡張子抜き)の値となります。こちらはトレーナー情報配列に追加しています。
トレーナー情報:園児 シホ(/Storage/Database/Trainer/preschooler/shiho.php)
<?php
return [
'id' => basename(__FILE__, '.php'),
'name' => 'シホ',
'money' => 50,
'lines' => [
'lose' => '負けちゃったー',
'win' => 'やったー!',
],
'party' => [
[ # 1匹目(ポッポ)
'class' => 'Poppo',
'level' => 3,
],
[ # 2匹目(ゼニガメ)
'class' => 'Zenigame',
'level' => 3,
],
],
];
ファイル読み込み時に「ゆびをふる」と同じ要領でファイル名を取得する方法もありましたが、こちらの方が処理自体は簡単で間違いが起こりにくいため、ファイルに個別で設定する方式をとりました。これで、もし同じトレーナーとの戦いを制限するとなった場合でも、ファイル名による判別が容易になります。
トレーナーとの戦闘記録は、勝敗に関わらず格納します。なので、トレーナー戦開始時にプレイヤー情報へセットしましょう。
トレーナー戦開始サービス(/App/Services/Battle/StartTrainerService.php)
/**
* @return void
*/
public function execute()
{
// 味方の選出
$this->electionFriend();
// トレーナーの情報の作成とポケモンの選出
$this->createTrainer();
// トレーナーとの戦闘記録をカウント
player()->setTrainer(trainer());
これでトレーナー情報の格納処理は完成です。 上限回数をチェックするために、コントローラーとフロント側でも回数を判定するための処理を追加しておきましょう。
ホームコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* バトル開始(トレーナー)の検証
* @return boolean
*/
protected function validationBattleTrainer(): bool
{
// バトル開始可能な状態かを確認
if(!player()->isFightParty()){
response()->setMessage('バトルに参加できるポケモンがいないので、戦えません');
return false;
}
// トレーナー情報・プレイヤーレベルの確認
if(
empty(config('trainer.'.request('trainer'))) ||
player()->getLevel() < config('trainer.'.request('trainer').'.level')
){
response()->setMessage('プレイヤーレベルが足りていません');
return false;
}
// 残り回数の確認
if(!player()->isFightTrainer(request('trainer'))){
response()->setMessage('本日のバトル上限回数を越えています');
return false;
}
return true;
}
トレーナーモーダル(/Resources/Partials/Home/Modals/Trainer.php)
<div class="modal-body">
<figure class="text-center position-relative" style="height:120px;">
<img src="" id="select-trainer-image" class="center-image d-soft-none">
</figure>
<div class="mb-2 p-1 text-center small bg-light rounded-sm">
<p class="text-muted mb-0" data-trainer="default">トレーナーを選択してください</p>
<?php foreach(config('trainer') as $key => $trainer): ?>
<?php if(player()->isFightTrainer($key)): ?>
<p class="text-success d-soft-none mb-0" data-trainer="<?=$key?>" data-fight="true">本日は残り<?=player()->getRemainingTrainerCount($key)?>回戦えます</p>
<?php else: ?>
<p class="text-danger d-soft-none mb-0" data-trainer="<?=$key?>" data-fight="false">本日はもう戦えません</p>
<?php endif; ?>
<?php endforeach; ?>
</div>
<div class="form-group">
<select class="form-control form-control-sm" name="trainer">
<option value="">-- 選択してください --</option>
<?php foreach(config('trainer') as $key => $trainer): ?>
<option value="<?=$key?>" <?php if($trainer['level'] > player()->getLevel()) echo 'disabled'; ?>><?=$trainer['name']?>(必要レベル:<?=$trainer['level']?>)</option>
<?php endforeach; ?>
</select>
</div>
<ul class="mb-0 pl-3 small">
<li class="text-muted">プレイヤーレベルが足りていないトレーナーとは戦えません</li>
<li class="text-muted">プレイヤーレベルは「メニュー」>「プレイヤー名」で確認できます</li>
</ul>
</div><!-- Modal body -->
ホーム画面用JS(/Public/Assets/js/Home/home.js)
/**
* トレーナー選択
* @function on:change
* @return void
**/
var selectTrainerInit = function(){
$('form#select-trainer-form [name="trainer"]').on('change', function(){
// テキストを全て非表示
$('p[data-trainer]').hide();
// 必要情報の取得
var trainer = $(this).val();
var form = $('form#select-trainer-form');
var image = $('#select-trainer-image');
var text = $('p[data-trainer="' + trainer + '"]');
if(trainer){
// 画像とテキストを表示
image.attr('src', '/Assets/img/npc/front/' + trainer + '.gif')
.show();
text.show();
// 戦える状態ならボタンを有効化
form.find('input[type="submit"]').prop('disabled', !text.data('fight'));
}else{
// デフォルトテキストを表示
$('p[data-trainer="default"]').show();
// 画像を非表示にしてフォームを無効化
image.attr('src', '')
.hide();
form.find('input[type="submit"]').prop('disabled', true);
}
});
}
戦った回数をカウントすることができました。これでトレーナー情報の記録処理が完成です。
日付による更新処理
トレーナー情報の格納処理が実装できましたが、保管したままだとそれ以降は戦えなくなってしまいます。なので、日付を確認することでプレイヤー情報を更新するという処理を加えていきましょう。
時間の記録
まずは時間の記録についてです。こちらは、セーブ時間の記録と同じ要領でDateTimeクラスを使って保管していきます。記録のタイミングはいくつかありますが、毎回更新していても余計な処理が動くことになってしまうため、ホーム画面の読み込み時のみ更新処理を行うようにしましょう。
ホームコントローラー(/App/Controllers/Home/HomeController.php)
// ホーム用コントローラー
class HomeController extends Controller
{
use HomeControllerTrait;
/**
* @return void
*/
public function __construct()
{
// 親コンストラクタの呼び出し
parent::__construct();
// 分岐処理
$this->branch();
// プレイヤー情報の最終更新日時を更新
player()->setUpdatedAt();
}
コンストラクタの分岐直後にプレイヤー情報を更新しました。デストラクタで行う場合はトースターやメッセージの格納タイミングを逃してしまうため、もし必要であればコンストラクタの処理内で実行する必要があります。
setUpdatedAtメソッドの内容は次項目でコード解説をします。
差分の確認
更新時刻の保存時に差分を確認、もし日付をまたいでいればトレーナー情報をリセットするという処理を加えます。
なので、setUpdatedAt内に分岐を追加しましょう。
プレイヤークラス(/Classes/Player.php)
/**
* 最終更新日時の格納
* @return void
*/
public function setUpdatedAt(): void
{
// 現在の日時を取得
$now = new DateTime;
// 更新前と比較
if($this->updated_at){
$old = new DateTime($this->updated_at);
$old->setTime(0,0,0);
// 差分が返ってくれば日付変更処理
if($now->diff($old, true)->format('%a')){
$this->changeTheDate();
response()->setToastr('info', '残りトレーナー戦の回数がリセットされました');
}
}
$this->updated_at = $now->format('Y-m-d H:i:s');
}
/**
* 日付変更時の処理
* @return void
*/
private function changeTheDate()
{
// トレーナーとの対戦記録を初期化
$this->trainers = [];
}
updated_at更新前に、旧時刻を取得して時刻に00:00:00をセット、diffを使って日付の差分を算出しています。差分が1日でもあればchangeTheDateメソッドを呼び出してトレーナー情報を初期化、トースターによる通知をしています。
もし日付の変更で初期化したいプロパティ等があれば、このメソッドにまとめることで一括処理が可能です。あくまで親メソッドであるsetUpdatedAtはホーム画面でのみ呼び出しているため、バトル中に初期化処理が呼び出されることはありません。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「ゲームバランスの調整編」としてDateTimeクラスを使った「トレーナーバトル上限回数の実装方法」をご紹介しました。
ゲームづくりやプログラミングに興味がある方は、ぜひ参考にしてみてくださいね。