ゲーム(仮称)_戦闘画面
戦闘
戦闘シーンの実装を考える
本編に来た感がある
戦闘画面
Battle.vue(モンスターの画像、味方のステータスとかが並んでいる)
https://scrapbox.io/files/66d324fa7cdd23001c42dd45.jpeg
上記なら、なんとか実装のイメージはできる
この辺の表示をステータスによって変えるわけだ
敵が現れたという戦闘開始ステータス(クリックでコマンドに遷移)
コマンド選択ステータス
1人目選択ステータス
コマンドを指定した後、敵を選択するステータスに遷移させる
2人目選択ステータス
3人目選択ステータス(確定する場合は、そこで確定の処理を入れる)
実行ステータス
選択情報を配列とかjsonで渡して実行処理をする。
敵の数 + 自分たちの数の分だけ表示を出す。
? この辺りは考える必要がありそう。どうやって順々に結果を出すのかとか。
裏で処理は終えて、その結果とかを配列にまとめてjsに渡す→クリックで1件ずつ表示させていくとかでいいかも。
ここもステータス処理なのか...?
この時点で敵を倒していなければ、コマンド選択ステータスに戻す。
敵を倒したという戦闘終了ステータス。必要なら結果リザルトも。
とりあえず
簡単にサンプルを作る方が手っ取り早い
レイアウト作成
ステータス整理
コマンドについて
data()で定義しておき、その値を格納させるのが楽かも。
通信タイミング
Battle.vueが映った時、
ユーザーに紐づくパーティーの情報
パーティー
フィールドに基づく敵の情報を取得する
マイグレーション時に考慮する要素とか
テーブルを用意する
中間テーブルは一旦考慮しない
$ php artisan make:migration create_rpg_parties_table
$ php artisan make:migration create_rpg_roles_table
$ php artisan make:migration create_rpg_enemies_table
$ php artisan make:migration create_rpg_skills_table
中間テーブル作る
rpg_partiesが現在覚えているrpg_skillesを格納すれば良い
table:中間(イメージ)
id rpg_party_id rpg_skill_id skill_level
1 2 5 2
2 2 6 2
中間テーブルの命名規則はまちまち。規則に従うというより、以下に中間テーブルであることがすぐわかるかが大事そう。なので、
$ php artisan make:migration create_rpg_party_learned_skills_table
モデル
$ php artisan make:model /Game/Rpg/Party
$ php artisan make:model /Game/Rpg/Role
$ php artisan make:model /Game/Rpg/Enemy
$ php artisan make:model /Game/Rpg/Skill
$ php artisan make:model /Game/Rpg/PartyLearnedSkill
24.9.6 戦闘処理まとめ
/game/rpg/battle
Vue
screenをbattleに変えて、画面をバトル処理にする
ステータスが'escape',resultLose,resultWin以外なら、getEncountData()でデータを取得しに行く。
(戦闘が終わっていなければ、battle_stateのjsonから途中データを引っ張ってくる必要があるので)
Laravel
戦闘を開始するためのステータスデータが作られ、vue側に渡される。
セッションIDで管理され、すでにデータが存在する(戦闘が途中で中断された)場合はそれを渡す。
Vue
受け取ったデータを分解し、プレイヤー情報、敵情報、セッションIDに分ける。
dataは[0]がプレイヤー、[1]が敵、[2]がセッションID。
問題なく呼び出すことができた場合、battleStatusをencountに移行する。
画面全体をクリックしたら、nextAction()のencount処理が走る。
this.partyData().lengthが0の場合、味方全滅のためresultLose処理に移る。
this.enemyData().lengthが0の場合、敵全滅のためresultWin処理に移る。
? 仲間を配列として消すと、負けた時に画面から消えてしまうので別途判断させた方が良いのかも
partyData[currentPartyMemberIndex]からコマンド処理を行うパーティメンバーを取得する
is_defeated_flagがfalseなら、battleStatusをcommandに移行する。
is_defeated_flagがtrueなら、currentPartyMemberIndexをインクリメントしてwhileを回し続ける
? 無限ループのバグが起きる可能性があるかも(trueのメンバーだけ格納された場合、永久に回る)。max3回?
コマンド選択肢をクリックし、handleCommandSelection('ATTACK')を動作させる
partyData[currentPartyMemberIndex]からコマンド処理を行うパーティメンバーを取得する
is_defeated_flagがfalseなら、selectedCommandに選択したidとコマンドを入れる
今回の場合、idはrpg_parties.idのこと
その後、battleStatusをenemySelectに移行する。
is_defeated_flagがtrueなら、currentPartyMemberIndexをインクリメントしてwhileを回し続ける
? nextAction()と同じ処理をしているので不要かも。
敵をクリックし、selectEnemy(enemyIndex)を起動させる
インデックスはenemy.enemy_indexで、これは敵の並びをエンカウント時にjsonで定義したもの。
enemySelectステータス以外は、敵をクリックしても何も動作させない。
partyData[currentPartyMemberIndex]からコマンド処理を行うパーティメンバーを取得する
storeに定義したsetSelectedEnemy(partyId, enemyIndex)で処理を行う
上で作成したselectedCommandステータスから、partyIdが一致するものを探す
その配列にenemyIndexを格納する
currentPartyMemberIndex < this.partyData.length - 1で、コマンドを続けて選択するかを選ぶ
今インデックスが0で、パーティが2人ならもう一人分選択する必要がある
その必要がないなら、execに移行する。
this.execBattleCommand()の実行
セッションIDと選択コマンドをpostで渡す。
code:選択コマンド.json
[
{ "partyId": 2, "command": "ATTACK", "enemyIndex": 2 },
{ "partyId": 3, "command": "ATTACK", "enemyIndex": 2 },
{ "partyId": 5, "command": "ATTACK", "enemyIndex": 2 }
]
Laravel
/api/game/rpg/battle/exec
セッションIDを参考にrpg_battle_stateを取得する
受け取ったコマンドと、現在のステータス情報を別配列に格納
$current_players_dataと$current_enemies_dataと$commands
戦闘実行前に、戦闘不能のデータをfilter()->values()で取り除き、インデックスを再割当てする
code:txt
この時の0と2のインデックスは、battle_states.players_json_dataで定義された順番になる
user_idで絞り込んでget()した順に並んでいるので、parties.id順になる
テストデータなら0,2なら、メイジちゃんとカア. それをずらして0,1にしている →これは後述する、敵が選択するtarget_players_indexの処理でrand()で選ぶ際に必要
→だが、これもrandじゃなくランダムに相手自体を取得すればいいのではないかと思った
$current_players_dataにコマンドとenemyIndexを格納する
enemyIndexはエンカウント時に定義した、敵の並び順を指す
value_spd順にデータを並べ、1つの配列に入れ直してforeachで戦闘結果を実行。
味方の行動
自分はやられフラグが立っている場合は、何も行わず次のループへ
$current_enemies_data()がemptyなら、何も行わず次のループへ
攻撃を選択した場合
攻撃対象として指定した敵が討伐済みなら、別の敵を指定する
value_strとvalue_defを参考にしてダメージ計算をする
ダメージは1以上なら、攻撃対象の体力をダメージ分削る
倒したならis_defeated_flagをtrueにして、次のループへ
倒すしていないならそのまま、次のループへ
ダメージが0以下なら、何もせず次のループへ
敵の行動
戦闘時(構想)
現在の情報を戦闘セッションとして管理する
自分の体力だとかその辺を。
ページリロードがあってもそこからやり直すことができる
イメージ(chatgptと相談しつつ)
流れ
セッションIDを作成し、クライアントに返す
この値のやり取りで戦闘のやり取りを行う。HPの削りなど。
コードイメージ
code:php
# 戦闘開始
$sessionId = uniqid(); // セッションIDを生成
$initialBattleState = [
'session_id' => $sessionId,
'player_data' => json_encode($playerData),
'enemy_data' => json_encode($enemyData),
'status' => 'ongoing', // 戦闘中
'created_at' => now(),
'updated_at' => now(),
];
# 戦闘更新
// 現在の戦闘データを取得
$battleSession = DB::table('battle_sessions')
->where('session_id', $sessionId)
->first();
if (!$battleSession) {
}
// 戦闘データを更新
$battleData = json_decode($battleSession->player_data, true);
// アクションに応じて戦闘データを更新するロジック
// 更新したデータをデータベースに保存
DB::table('battle_sessions')
->where('session_id', $sessionId)
->update([
'player_data' => json_encode($battleData),
'updated_at' => now(),
]);
みたいな感じ。
jsonでプレイヤー情報全部入れて、その値をやり取りする感じ。
上の実装をベースに、1こずつ考えてみよう(セッションIDはexecステータスの時に考えよう)
encount時
味方の名前, HP/MP, スキル一覧, 敵のHP/MP, 画像パスを取得する
command時 / enemySelect時
とりあえず選択したコマンドを配列に入れたい。
なんのコマンドを選択したのかをさらにサブステータスとして管理する?
とりあえず期待する動きは実現できてそう。
? 実装を読み解いてみる
store.js
コマンドを選択した状態、敵を選択した状態、今何人目のコマンドを選択しているのかをstateとして定義する。
code:js
setSelectedCommand(state, { partyMember, command }) {
state.selectedCommands.push({ partyMember, command });
},
まず基礎
{}: オブジェクト配列のこと。(name: 'Alice'みたいな感じ)
[]:
(state, { partyMember, command })
オブジェクトの分割代入
code:例.js
// ここでオブジェクトとして渡しているキーを指定することで、その中身を取得できる
function greet({ name, age }) {
console.log(Hello, ${name}. You are ${age} years old.);
}
const person = { name: 'Alice-chan', age: 30 };
greet(person);
なので上の例では、person = { name:ではなく、nickname:とすると、キーが違うので取得できなくなる
キーを指定して渡すオブジェクトのキーの値を取得できるよというイメージで良い。
なのでそれらの値を格納して、selectedCommandsには下記の値が入る。
code:js
敵対象選択時も同様。
code:js
setSelectedEnemy(state, { partyMember, enemyId }) {
const commandIndex = state.selectedCommands.findIndex(c => c.partyMember === partyMember);
if (commandIndex !== -1) {
}
},
const commandIndex
findIndexでpartyMemberが一致するコマンドを、selectedCommandsから検索
つまり
code:js
// 選ぶ前のselectedCommands
この"partyMember"の値と、今回引数として渡されるpartyMemberを===で比較している
一致してたら新しい要素としてenemyも入れときましょうという感じ。
元々入ってるステータスの名前と、渡す名前が正しいなら入れるというだけ。
if (commandIndex !== -1)
要するに該当するコマンドが見つかったのなら、selectedCommands[0]にenemyIdを追加する
対象選択後のselectedCommands
code:js
Battle.vue
code:js
selectEnemy(enemyIndex) {
if (this.battleStatus !== "enemySelect") return; // 敵選択中以外に敵をクリックした場合は何もさせない。
this.$store.dispatch('setSelectedEnemy', { partyMember: currentMember.nickname, enemyIndex: enemyIndex });
if (this.$store.state.currentPartyMemberIndex < this.partyData.length - 1) {
this.$store.dispatch('incrementPartyMemberIndex');
this.$store.dispatch('setBattleStatus', 'command');
} else {
this.$store.dispatch('setBattleStatus', 'exec');
}
},
v-forで敵を回しているので、forで回しているindexを取得して、
現在のstateのindex情報を参考にpartyData(パーティ情報)を取得して、そこに敵のindexを入れる
3人分揃うまでこの処理を続けて、揃ったらexecする。
戦闘処理(exec)
axiosでlaravel側にpostする。
code:state.selectedCommandsのデータ
[
{ "partyMember": "カア", "command": "ATTACK", "enemyIndex": 0 },
{ "partyMember": "メイジちゃん", "command": "ATTACK", "enemyIndex": 1 },
{ "partyMember": "パラ", "command": "ATTACK", "enemyIndex": 1 }
]
これは$requestから下記の形で受け取れる。
code:php
$commands = $request->all();
array:3 [▼
0 => array:3 [▼
"partyMember" => "カア"
"command" => "ATTACK"
"enemyIndex" => 0
]
]
思いつき
戦闘開始時点でセッションを作る
execの部分でそのセッションIDを呼び出し、色々計算していけばいい
index1の相手に攻撃してとか。
戦闘開始時に処理を追加する
戦闘セッションを追加する
$ php artisan make:migration create_rpg_battle_states_table
$ php artisan make:model /Game/Rpg/BattleState
流れ2
セッションIDをエンカウント時に作成し、戦闘データと一緒に渡す
vue側で受け取り、stateで保管する
execする時、一緒にセッションIDを渡す。
戦闘処理を考えよう
とりあえず詳細な計算式とかは空にしといて、ガワだけ作ってしまう
攻撃メソッドとかを定義して、計算式を後から変えられるようにしておけばOK
戦闘に必要なもの
コマンド
内容
選択したメンバーのparty_ID
rpg_partiesの情報全部
rpg_enemiesの情報全部
現在の戦闘データ(rpg_battle_statesのjson)
配列イメージ
code:php
// 理想の配列
$party_member = collect([
'id' => $party_id,
'nickname' => 'カア',
'command' => 'ATTACK',
'target_enemy_index' => '', // ここをどうにか定める indexかな?
'value_hp' => '100',
'value_ap' => '25',
'value_str' => '10',
'value_def' => '4',
'value_int' => '1',
'value_spd' => '2',
'value_luc' => '0',
)
$enemy = collect([
'id' => $enemy_id,
'name' => 'すらら',
// コマンドと対象は攻撃処理のときにランダムで指定すれば良い
// あとはステータス
]);
一番楽な方法は、jsonに全てぶち込む。
リアルタイムで変動する値は全部jsonで管理すれば良い
コマンドとjsonを紐づける
code:php
# これと
Illuminate\Support\Collection {#489 ▼
0 => {#444 ▼
+"id": 1
+"items": []
+"command": null
+"nickname": "カア"
+"value_ap": 25
+"value_hp": 100
+"value_def": 10
+"value_int": 5
+"value_luc": 10
+"value_spd": 25
+"value_str": 20
+"role_portrait": "aaa.png"
+"target_enemy_index": null
# これ
Illuminate\Support\Collection {#485 ▼
0 => array:3 [▼
"partyMember" => "カア"
"command" => "ATTACK"
"enemyIndex" => 0
]
ぜ〜ったいにID
"partyMember" => "カア"をpartyIdにする。
ついでにプロフィール画面のバグ直す
敵を倒そう
enemyのjsonデータに、敵を倒したかどうかのフラグをつける
is_defeated_flagかな
敵と味方両方につけとくと良い
やられたあと
モンスターはvue側の画面から除外しておく(jsonには残して、そのあとリザルトで使えば良い)
vue側でそのフラグが立っていた場合、モンスターを表示する配列から外せばOK
モンスターを配列から外す処理の場合、最初の敵の配列がズレて最初の対象に当たってしまう
エンカウント時に、固定される配列番号を割り振る
enemyIndexなどを現在指定しているが、そうでなくその番号を指定する
倒す前はenemyIndexとその番号は一緒だが、倒した後(配列がfilterされた後)ずれることがなくなる
これは味方も同様で、味方がやられた場合も制御できるようにする
敵が終わったらcurrentPartyMemberIndexを調整しよう(1こずつやってくのが良い)
味方側も調整する
挙動について
戦闘終了を押した時、battlestateを削除する
不具合がある。削除後にまた作られている
戦闘の仕様を変える
is_defeated_flag準拠で考えるようにする。一旦全部消す
今コマンドを選択している人物を${this.partyData[this.currentPartyMemberIndex]で考えているが、これは別にいい
currentMemberPartyIndexの初期値を設定しないとダメ
cssの設定部分でエラーが出る
動作は問題ないが、コマンド選択画面を一瞬遷移するのでそこでエラーになる
敵選択後にcommandに写って、それからexecが走るのでその部分。
→無事に帰れた
フィールドのIDを指定する
code:js
// router.js
{
// path: '/game/rpg/battle',
path: '/game/rpg/battle/:field_id',
name: 'game.rpg.battle',
component: Battle
},
// Adventure.vue側の「行く」ボタン
startBattle(stageId) {
this.$store.dispatch('setScreen', 'battle');
// this.$router.push('/game/rpg/battle');
this.$router.push(/game/rpg/battle/${stageId});
}
// エンカウント時のaxios
let field_id = this.$route.params.field_id; // router.jsのpathで定義した値を取得できる
axios.post(/api/game/rpg/battle/encount,{
field_id: field_id // $request->field_idとしてlaravelで受け取れるようになる
})
これでid=1を押すと、/game/rpg/battle/1に遷移することができる
(あとで)フィールドとステージを決めよう
1-1, 1-2みたいな感じでやってみようか 最大5段階で
24.9.12
敵の絵を出し分ける
v-bindを使って動的にスタイルをバインディングする
code:js
<div @click="selectEnemy(enemy.enemy_index)"
:style="{ backgroundImage: 'url(/storage/rpg/enemy/' + enemy.portrait + ')'}"
:class="{ 'enemy-picture': true, 'enemy-hover-active': battleStatus === 'enemySelect' }"
こんな感じ
クラスも動的にバインディングしている。
'enemy-picture': true,
enemy-pictureクラスを常に適用させる。
'enemy-hover-active': battleStatus === 'enemySelect'
enemy-hover-activeクラスを特定のステータスの時だけ割り当てる。
次にやること
フィールドのIDを指定して、フィールドにあった敵が出現するようにする
一番楽なのは、1-1, 1-2とかで出現する敵のプリセットを用意する
これならレベルとかも平等に上がるし、攻略もしやすい
あいつの対策はどうとか、アイテム買い込むかとか
1-1を周回してレベリングすることもできる
別のコマンドの実装をする
レベルについて考える
同じプリセットを倒してみんな同じレベルになってたら、個性がなくなるかも?
スキル振りで差分をつけていけばいいか。
ステージをテンプレートにする
草原-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メソッド分けてたけど、統一する
同じ変数を定義するのが面倒。
そうした
24.11.25 デモ版を作りたいので、詰める
負けた時の挙動を変更する
現状繰り返していればレベルが上がっちゃうので、それはやめよう
低レベルクリア目指してて途中で負けて、1からやり直すことになるがそこでレベルが上がると萎える。
ということは
battle_sessionで、現在のexpやレベル、使い切ったアイテムなどを全て確保させる必要がある
逃げたり、完全にクリアした場合にDBに本情報として格納する感じ
見直す
戦闘開始時、プレイヤーデータにtotal_exp,levelの情報も格納する
そうした。
ついでにfreely...関連のポイントも持たせた
レベルが上がったら、battlestateのjsonのデータでレベルアップ処理をさせる
既存リファクタリングより書き直した方が早い
一応反映まではかけた。
そのようにした
ステータスポイントとスキルポイントがレベルアップした時に振り分けられるようにした
戦闘勝利後について
HP, APを少し回復してあげよう
戦闘不能者は少し厳しくいく。回復なしで。
(メディックと蘇生薬の価値をあげよう)
逃げるコマンドと全滅時の挙動について
逃げる
その時の戦闘状態のステータス, アイテム状況のまま、メニューに帰ることができるようにする
素早さ依存で成功したら逃げられるようにしようか。
全滅
battle_stateを破棄し、戦闘前の状態に戻るようにする
まあ他に悪いことはなくてもいいかな。萎えるし。
デモ版todo
クリア時やステージ解放フラグの設定
rpg_savedata_cleared_stagesテーブルを作って管理すれば良い
攻撃バランスの調整
現状物理攻撃と防御の関係が極端すぎる。HPの伸びを高くして、防御の価値を下げたい
魔導士とかがすぐ死にすぎである
パラは無駄なステータスがなく、優秀すぎである
敵のスキル処理実装
ボスを固定の動きにする
やり込むときに運要素にならなくなるのでいいと思う。あと意図的に難しくしやすい。
LUCに意味を持たせねば
防御コマンド実装