ゲーム(仮称)_戦闘関連の処理
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を周回してレベリングすることもできる
別のコマンドの実装をする
レベルについて考える
同じプリセットを倒してみんな同じレベルになってたら、個性がなくなるかも?
スキル振りで差分をつけていけばいいか。