ゲーム(仮称)_戦闘画面
戦闘
ステージをテンプレートにする
草原-1 ~ 草原-5くらいまで。5がボスで、クリアできると次のステージが開放される
table:イメージ_rpg_preset appeerence enemy
id field_id stage_id enemy_id number
1 1 1 1 2
2 1 1 2 1
3 1 2 1 1
4 1 2 4 1
5 1 3 2 3
もしくは、ステージごとに敵を重み付けする
1-1ならコスト3までの敵をランダムに取るとか
1-4ならコスト6までの敵をランダムに取るとか
まあ管理しやすいのは↑のテーブルのやつかな
rpg_enemiesのappear_stage_idをつけてもいい。
ただし同じ敵を出す時大変になる
rpg_preset_appeerence_enemyかな
ランダムで出す場合は、進行型のRPGを作る場合に考えれば良い。
今回は短期周回コンセプトなので固定の方がいい
$ php artisan make:migration create_rpg_preset_appearing_enemies_table
$ php artisan make:model /Game/Rpg/PresetAppearingEnemy
できた!
Lvアップさせたい
敵に経験値カラムを追加する
味方に経験値テーブルなどを用意する
レベルアップの状態遷移を挟む
敵を倒した!経験値xポイントを獲得
レベルが上がった! (戦闘不能なメンバーにも経験値を割り振るかどうかはあとで。一旦各自に累計expを持たせる)
ステータスアップ処理とか。
現段階でデータベースのカラムを増やす
enemies
exp
skills
is_enemy: 敵が使うスキルかどうか。
これはavailable_role_idを9とかにして判断すればいいか!
$ php artisan make:migration add_columns_to_rpg_enemies_table --table=rpg_enemies
パーティに累計の経験値もtotal_exp追加しておこう
$ php artisan make:migration add_columns_to_rpg_parties_table --table=rpg_parties
経験値テーブルも
table:exp
id level total_exp
1 1 0
2 2 10
3 3 30
4 4 80
5 5 210
$ php artisan make:migration create_rpg_exps_table
敵を倒した後の処理を決定しよう
resultWinでクリックした時、exp獲得の文字も入れれば良い
jsonに入れればOK
$ php artisan make:model /Game/Rpg/Exp
ステータスの上がり方
rand()とgrowth_hpなどを使って伸ばしていくのはどうだろう。
計算式を考えたい
最低値を伸ばしてあげたほうがいい
strで格闘家の場合
STRのデフォルトの伸び幅のベースを1と定義する
growth_strは5なので、 rand(0,4) というデフォルト値をrand(5,9)とかで回す
大雑把すぎるか?でもいいか?
strで魔導士の場合
growth_strは1なので、 rand(0,4)をrand(1,5)とかで回す
調べてみると、ガウス分布というシステムがいいかもしれないらしい
https://scrapbox.io/files/66ddb509f47ff9001c0b08b2.png
とりあえず、calculateGaussianGrowth($mean)というガウス分布関数を作ってもらった
$meanには$growth_...の値を入れる
code:tinker.php
use App\Models\Game\Rpg\Party;
Party::calculateGaussianGrowth(5); // これで試せる
# 10回やってみた 合計49ポイント
= 6.0 = 2.0 = 5.0 = 6.0 = 5.0 = 4.0 = 6.0 = 5.0 = 5.0 = 5.0
# もう10かい 合計48ポイント
= 5.0 = 6.0 = 5.0 = 5.0 = 3.0 = 6.0 = 4.0 = 5.0 = 5.0 = 4.0
# 次やったら 52になった。いい感じのブレがある
めっちゃいいじゃん!
+9が出る確率は分散を設定するとできるようになる
1.0で+9が出る確率 = 約0.00003% 2.0は0.6%, 3.0なら2.1%, 4.0なら4.7%
ただし、分散を弄るとへたる確率も上がる(+1とかが出てしまう)
一旦1.0で計算すればいいか。
レベルが上がった時のステータス上昇設定を作る
ガウス分布に各成長ステータスを渡す
戦闘処理詰める
ステージ進行フラグみたいなもので、URLを叩いて1-2から進めないようにさせる。
HPやMP情報は保持させる。
いきなりクリアできず帰ることが前提とした難易度に調整する
ステージ跨ぐとちょっと回復とか、スキル振りをクリア時点でできるようにする
この辺の調整は大変なので、あとで。
逃げるとその時点での経験値を入手したまま逃げられるとかにする。
ステージ進行が順々にできるようにしよう
ステージ1以外には、1からちゃんと遷移してきたかどうかの確認をとる
vue側でclearStageというステータスを作って制御した。
rpg_battle_statesに現在ステージの表記を入れる。1-2とか。
ロールバックしてカラムを整える
テーブルを手動で消して
migrationsテーブルからマイグレーション情報を消すと、擬似的にロールバックできる
code:php
if ($battle_state->current_field_id == $field_id && $battle_state->current_stage_id == $stage_id) {
Debugbar::debug("現URLとbattle_stateのデータの整合性が確認できました。現在: {$field_id}-{$stage_id} battle_state: {$battle_state->current_field_id}-{$battle_state->current_stage_id}");
} else {
return abort(500, '現URLとbattle_stateのデータの整合性が確認できませんでした。');
}
これでエラーになった場合、強制的にバトルセッションが消えてしまうのでかわいそうではある
でもなあ。暇なら調整してやる。
また、クリアした直後にリロードするとデータが消えてしまう
これはかわいそうなので、なんとかしてあげたい
とりあえず実装できた。
HP, MPをステージ遷移時に保持する
クリア時点で、json配列を作る
code:json
[
{ "partyId": 1, "current_hp": 24, "current_ap": 30 },
{ "partyId": 2, "current_hp": 54, "current_ap": 8 },
{ "partyId": 3, "current_hp": 40, "current_ap": 12 },
]
これで次回も引き渡すようにすれば良い
レベルが上がった時は回復させてあげよう
もしくは、戦闘勝利時点の処理中にbattlestateから引っ張って来れないだろうか。
セッションとかでも実装可能だが、セッションを消すと不正されてしまうだろう。
現時点で戦闘勝利時点でリロードした場合は、ステージ最初時点に戻る。
? 現時点でのステージ進行を全てjsonで管理した方がいいのかも
レベルが上がったらその中だけで管理するとか。いやでも結構大変だ...
カジュアルにするならレベルが上がったら逃げたりやられたりしても上がったままにすれば良い
繰り返していればクリアできる(ただし使ったアイテムは減る)
とりあえずHP/MPは戦闘継続でいこう
実装
戦闘勝利時点で次のbattle_stateを作る形にする
HP/MPをその時点の20%回復させたものにした。戦闘不能時は10%。
新しい問題
戦闘勝利時点で「逃げる」を選択すると、battle_stateが作られた時点で逃げてしまい正しくstateが消えない。
なので、勝利時点で逃げるボタンを無効にしておく
todo
スキルを戦闘画面に出す
いよいよだ。とりあえずやること一覧を並べてみる
現時点で習得しているスキルをjsonに入れる
既にできてる
画面でスキルにカーソルを合わせた時、選択肢を追加で出す
スキルが選ばれた時、実行時にそのスキルにあった処理を追加する
画面にスキルの情報を出す
Vuexのgetterで実装ができるかもしれない
stateから派生したデータを取得するためのもの
stateの値を加工したりフィルタリングすることができる。
vueのcomputedと似た役割を持つ
今回
stateでlaravelから取得したpartyDataを格納する
getterで2つ定義
partyDataから現在選択中のユーザーの情報を加工し取得する配列を作る
上で作った配列からスキルだけを取り出す配列を作る
今回はpartyData[currentPartyMemberIndex]からスキルデータを出力するために加工すると言うイメージか。
レイアウト
Droprightを使う
スキルにマウスオーバーした時、特定のスキルの説明を出す
スキル選択時、どのスキルを選んだのかを格納する
? コマンドごとに格納する配列の種類を変えられたら良い
code:json
// 攻撃の時
battle.command.[
{
"partyId": 2,
"command": {
"selected": "ATTACK",
"enemyIndex": "0",
}
},...
// スキルの時
{
"partyId": 3,
"command": {
"selected": "SKILL",
"skill_id": 1,
"enemyIndex": 0, // もしくはスキルのタイプが全体攻撃ならALLに設定するとか。
"partyIndex": 1, // 回復系とか味方を守るスキルの時。
}
// アイテムの時
// ... みたいな感じで。
]
色々なスキルの処理を作ろう
攻撃
単体攻撃 ✅
全体攻撃 ✅
回復
単体回復 ✅
全体回復 ✅
バフ
味方単体 ✅
味方全体 ✅
新しい状態partySelectを作る
storeに新しいステータスを作る
setSelectedPartyをmutationsとactionsに。
固定データがたくさんあるので、シーダーで管理しよう
$ php artisan db:seed
rpg_skills
database/seeds/Rpg直下で管理するようにしようか
$ php artisan make:seeder rpg/SkillTableSeeder
rpg_roles
$ php artisan make:seeder rpg/RoleTableSeeder
rpg_items
rpg_enemies
$ php artisan make:seeder rpg/EnemyTableSeeder
rpg_preset_appearing_enemies
rpg_fields
テスト用のシーダー
$ php artisan make:seeder rpg/PartyLearnedSkillTableSeeder
シーダー作って
composer dump-autoloadしてdb:seedする
シーダーがうまく動作しない
code:txt
php artisan db:seed
INFO Seeding database.
Illuminate\Contracts\Container\BindingResolutionException
at vendor/laravel/framework/src/Illuminate/Container/Container.php:940
936▕
937▕ try {
938▕ $reflector = new ReflectionClass($concrete);
939▕ } catch (ReflectionException $e) {
➜ 940▕ throw new BindingResolutionException("Target class $concrete does not exist.", 0, $e); 941▕ }
942▕
943▕ // If the type is not instantiable, the developer is attempting to resolve
944▕ // an abstract type such as an Interface or Abstract Class and there is
laravel6から11に挙げたことで、いろいろ変わったのかも
seedsというフォルダ名をseedersに変更
composer.jsonを調整
code:composer.json
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeders", // "database/seeds"を変更
"database/factories"
]
},
これでcomposer-autoload
code:txt
root@ea4de5b5a079:/var/www/laravel_kirthread# php artisan db:seed
INFO Seeding database.
Database\Seeders\rpg\SkillTableSeeder..... RUNNING
Database\Seeders\rpg\SkillTableSeeder..... 117 ms DONE
うまく動作した!
? ブログにできると思う
物理攻撃か魔法攻撃かを分ける
スキルをより深く分割しよう
物理/魔法か
単体/全体か
攻撃/回復/バフか
属性は
decideExec{Job}Skillで計算するダメージをピュアにしよう
それで、ダメージ計算にかける時に相手の守備や魔防を考慮するようにする。
MPが足りないとスキルが使えないようにする
vue側でMPがないと使えないようにする
laravelのスキル側でMPがないと使えないようにする
(vue側で選択できた→敵がMPを減らすスキルなどを使われた場合のケースを考慮できる)
1つ戻るコマンドが欲しい...
最悪「やり直す」とかでもいい。これならすぐ実装できそう
バフスキルの実装
素早さソートの部分で、まず優先度高く設定する
code:php
$sorted_data = $players_and_enemies_data->sort(function ($a, $b) {
// 'DEFENCE'コマンドを持っているキャラクターを優先する
if ($a->command === 'DEFENCE' && $b->command !== 'DEFENCE') {
return -1; // $aが先に行動
}
if ($b->command === 'DEFENCE' && $a->command !== 'DEFENCE') {
return 1; // $bが先に行動
}
// 両方とも'DEFENCE'を選択していない、または両方とも'DEFENCE'の場合は、速度順で降順ソート
return $b->value_spd <=> $a->value_spd;
})->values();
laravelのsort()メソッド
phpのusort()と似ていて、コレクションを並び替えるために使う
$aと$bはソートアルゴリズムに合うような値をsort, usort側で勝手に管理してくれる
-1を返すと$aの方が優先度が高くなる
1を返すと$bの方が優先度が高くなる
宇宙船演算子の部分
$b <=> $a の結果で
$bが$aより大きい場合:1
$bが$aと等しい場合:0
$bが$aより小さい場合:-1が返る。
今回の場合、bのほうがスピードが高い時は1が返る→bが先に行動するという形になる
ターン数を決める
スキルカラムに持たせよう
lv1_buff_turnみたいな感じでいいと思う
付与されているバフを管理する
こいつらもjsonに持たせる
code:php
[
'buffed_skill_id' => 31,
'buffed_def' => 20,
'buffed_int' => -10, // マイナスもありうる
新しいスキルタイプを作る
ワイドガードはattack_typeタイプを0
他にもダメージ軽減スキルとかはバフじゃない新しいtype扱いにする。
target_rangeはself
effect_typeはSPECIALとした
特殊な処理は個別に関数を作って実装する
特殊スキルで個別調整が必要な部分
$opponents_dataに敵味方どちらを入れるのか振り分ける
バフの結果を攻撃式 / 防御式 / 行動順決定時も考慮するようにする
順序よくやっていこう
通常攻撃
味方と敵
自分の攻撃力と相手の防御力に付与する
スキル
各スキルの計算式
味方と敵
物理攻撃と魔法攻撃(計算後の防御力 or 計算後の魔法防御力)
単体と全体に対して書く
結構大変だぞ
今はstrとdefとintだけだが、HPのバフとかが増えるとめっちゃ大変になりそう
そもそもスキルの部分でダメージ計算式があるので、スキルの数だけこのままだと関数を挟む必要がある
どうすればいいだろう...
配列に、total_value_strみたいなものを作ってそこに入れるのがいいのかもしれない
とりあえず入れてみて、後でリファクタリングしてもいいかもね
RETURNした時にメイが戦闘不能になっていた時、メイから始まってしまう
https://scrapbox.io/files/66f5a198a76de7001c1a4437.png
RETURNにbattleCommandSetup()を追加して解消した。
ワイドガードが効かない...
オブジェクトか連想配列化の問題だった
code:php
if (isset($buff->$actual_status_name)) { // 2
文法としてはどっちもエラーは発生しないが、
$buffがオブジェクトの場合、1で書くとelse分岐に飛ぶ
$buffが連想配列の場合、2で書くとelse分岐に飛ぶ
そのため、正しくバフ加算がされていなかった。
バフによって、オブジェクトか連想配列か変わっている気がする...
↑でワイドガードを動かせるようにしたら、バトルメイジのバフでエラーが起こるようになってしまった
ワイドガードは即時発動なので配列だが、バトルメイジなどはjsonに渡して再度取得しているから型が違うのかも
型を厳格に管理しなければならない
タイプヒントをつけて解決した。
selected_skillなどが配列に残ったままなので、戦闘1ターンが終わった後空に戻す
ITEMを選んだ時の挙動がおかしいだけだったので、そっちを直せば修正された。
バフがそのターン中にかかった時、効いてない
(メイにバフが付与された後攻撃されているが、守備が適用されてない)
https://scrapbox.io/files/66f7cf19fd6087001c60a094.png
バフおかしいな
初回にかけた時はbuffがArray
次回以降のターンから、buffがObjectとして扱われている
バフをcollectionで最初から定義すればいいのでは。
タイプヒントをarrayとしているので、collectionをarrayに戻すようにした。
敵倒した後に発動するのを調整する
https://scrapbox.io/files/66f050b9374cf0001c1655ee.png
コマンド選択前に、対象が全滅したかどうかを判定する関数を挟んで解決した。
処理が多くなってきたのでprod環境で動くかちょっと確認したい
ルーティングを消して
publicに画像ファイルなどを配置して
画像をgit管理する。
publicにimageフォルダを作成し、そちらに画像を配置する
というか、storageは基本ユーザーがアップした画像などを配置する場所である
http://localhost:8080/image/rpg/character/portrait/portrait_mage.png
http://localhost:8080/image/rpg/enemy/gao.png
http://localhost:8080/image/rpg/field/grassland.png
これでおk
データベースからデータを持ってくる
バトルログを別の場所に溜めておきたい。
戦闘中参考になる情報なので、後から追えるように。
別の配列に貯めるようにした
アイテム作る!これができたら大体OKだと思う
基本的な流れはスキルと一緒。戦闘開始時に配列に現在持っているアイテムを渡して、jsonのデータを弄れば良い
itemsテーブルのカラムを調整する
基本的にスキルと同じカテゴリを持たせる
name, price, description
attack_type: なし/物理/魔法
effect_type: 攻撃/回復/バフ/その他
target_range:自身 / 単体/全体
is_percentage_based: 体力の何割かを回復するアイテムなのかどうか
percent: trueの場合、その割合%
fixed_value: falseの場合、その固定値(攻撃なども含む)
buff_turn: バフアイテムだった場合のターン
elemental_id: 属性。一応つけとくか。
max_possesion: 持てる数の最大
❣️️追加: 戦闘中に使えるアイテムなのかどうかのフラグ。
マイグレーションとシーダー作成
$ php artisan make:migration add_columns_to_rpg_items_table --table=rpg_items
$ php artisan make:seeder rpg/ItemTableSeeder
ショップに並べる
is_buyableで決める
買ったものと買ったセーブデータを紐付ける
$ php artisan make:migration create_rpg_savedata_has_items_table
ショップ関連情報を取得
? ショップ購入画面はのちにちゃんと組み立てるようにするので一旦放置
今が最低限のものなので。
検証用アイテム所持シーダーを作る
$ php artisan make:seeder rpg/ItemTableSeeder
アイテム配列の実装
プレイヤーに持たせるのではなく、個別に持たせようか。
battle_stateにitem_json_dataを持たせてもいいかも。
戦闘が終わったらexpと一緒に反映していく
実装する
アイテム選ぶところまでは作った
次やること
戦闘勝利時点で、アイテムの所持結果を反映させるようにする ✅
SavedataHasItemとItemにリレーションをかける
多対多だと思う。
違った。ここに限っては1:1である
SavedataとItemは多対多だが、中間テーブルであるSavedataHasItemはどちらとも1:1である
反映させるようにした
ヒール系、バフ系の実装もする✅
物理攻撃/魔法攻撃系アイテムも作って検証する✅
? スキルのバフとアイテムのバフを同時に使用した時、噛み合わないことがある?✅
正しくは同じターン中の単体アイテム→全体アイテムの時
直した。
アイテムのバフ処理にちょっとバグが残ってるかもしれない✅
ローカルで再現した。敵の攻撃時のダメージのバフ計算でバグってる
ワイドガードにbuffed_fromをつけてなかった。
テーブル関連を修正
rpg_parties
user_idをsavedata_idにした。
rpg_role_idを他のテーブルと同じく、role_idとrpgを端折る表示にした
rpg_weapon_idをequip_item_idにした
最悪別テーブルに分ける。
というか実装検討のものはとりあえずマイグレーションする必要はなさそうだ。
(その時にマイグレーションファイルを作れば良い。)
rpg_battle_states
user_idをsavedata_idにした。
rpg_party_learned_skills
rpg_party_idをparty_id
rpg_skill_idをskill_id
職業別にスキルexecメソッド分けてたけど、統一する
同じ変数を定義するのが面倒。
そうした