ゲーム(仮称)
とりあえず...
Laravel側
ベースの画面までのMVCを作る。
$ php artisan make:controller /Game/Rpg/IndexController
購入できるアイテムのマイグレーションファイルを作る。
$ php artisan make:migration create_rpg_items_table
? モデルも一緒に作りたいので別のマイグレーションファイル作る時に検証しとく。
アイテムの詳細(一旦)
id
name
price
description
マイグレーション
$ php artisan migrate
$ php artisan migrate:rollback --step=1
モデル作成
$ php artisan make:model /Game/Rpg/Item
ショップ処理を軽く作りたいので、セーブデータDBを作る
id
user_id
所持金
一旦汎用的なmoneyにしておこうか
未振り分けのスキルポイントとか unspent_skill_points
所持してるアイテムとか、その数とか(別テーブル)
ステータス系
別テーブルだけど。
health, max_health, inventory(所持アイテムリスト), equipped_item(装備アイテム),abillities(スキル),
プレイ時間play_time,
難易度 difficulty
$ php artisan make:migration create_rpg_savedatas_table
モデルも一緒に作ろう
正確には、モデル作成と一緒にマイグレーションファイルを作ることができる
$ php artisan make:model /Game/Rpg/SaveData -m
これだとテーブル名など指定はできない。じゃあモデルと個別で作った方が良さそうだ。
モデル作成
$ php artisan make:model /Game/Rpg/SaveData
戦闘のイメージを考える
冒険へ行く→ステージ選択→戦闘画面へ
ステージ画面作る
$ php artisan make:migration create_rpg_fields_table
status側にステージ解放フラグがあってもいいかも。
$ php artisan make:model /Game/Rpg/Field
? 状態を管理する必要がある。
vuexで実現ができる。
今の画面構成
メニュー画面
フィールド選択
ショップ
スキル振り
バトル画面 みたいな感じ。だいたい合ってるか?
タイトル画面も作ったら結構ページ全体のイメージが掴めそう。作ってみよう
冒険→戦闘画面遷移への現状の流れのイメージ
store.js
Vuexストアを作成し、状態を定義する
mutationsとactionsの関係
同じメソッド名を使うことが多い。
mutations
stateを直接変更するためのメソッド。常に引数としてstateを持つのが基本。
actions
mutationsをコミット(呼び出し)するためのメソッド
非同期処理が使える。
APIを呼んで処理が終わった後にmutationsを呼んで処理を行なったりできる
同じ名前で定義することで、どのmutationsを呼ぶかが明確になるメリットがある。
app.js
定義したvuexストアをアプリ全体で使えるようにインジェクトする(app.use(store))
App.vue
computedで現在のstate.isInBattleを取得
...mapState(['isInBattle'])// Vuex store.jsの状態を取得
これによってコンポーネント内でthis.isInBattleとしてstore.jsのstate.isInBattleにアクセスできる
これで戦闘中かどうかをv-ifで判定できるようになった。
Battle.vue
コンポーネントからisInBattleの状態を操作している
code:js
export default {
created() {
// app.useで"store"として定義したvuexのactionsをdispatchで呼んでいる
this.$store.dispatch('startBattle');
},
methods: {
endBattle() {
// 同様に"store"や"router"として定義したメソッドを呼んでいる
this.$store.dispatch('endBattle');
this.$router.push('/game/rpg/shop');
}
}
}
タイトル画面を作ってみる
store.jsで定義
stateとしてタイトル画面であること
mutationsとしてタイトル画面であることを表すメソッド
actionsでコンポーネント側からmutationsを呼び出すことができるようにするメソッド
currentScreenの構成を考える
/game/rpg:タイトル
/game/rpg/menu: メニュー画面
/game/rpg/menu/adventure: フィールド選択
/game/rpg/menu/shop: ショップ
/game/rpg/menu/skill: スキル振りなど、ステータス画面
/game/rpg/battle/:field_id: 戦闘画面
テキスト部分
敵表示部分
味方表示・味方コマンド選択部分みたいなイメージ。
→まずはタイトル画面から遷移できる、メニュー画面を作る
これが作れたら他の部分にたくさん流用できる知識がつくはず。
Menu.vueを作って、その中の子要素にadventure, shop, skillを作る。
これはルーティングでchildrenを定義することで実現ができる。
code:js
{
path: '/game/rpg/menu',
name: 'game.rpg.menu',
component: Menu,
children: [
{
path: 'adventure',
component: Adventure
},
{
path: 'shop',
component: Shop
},
{
path: 'Skill',
component: Skill
}
]
},
終わったら、各コンポーネントでstateを変える処理を書いていく。
this.$store.dispatch('setScreen', 'menu');
created()とmounted()の違い
mountedはDOMに依存する処理を書く時に使う。
vuexの操作はDOMに依存しないので、ステータス変更などはcreated()に書けば良い。
todo
戦闘シーンの実装を考える
本編に来た感がある
戦闘画面
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
戦闘
ステージをテンプレートにする
草原-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のデータを弄れば良い
todo
アイテム処理
敵のスキル処理実装
最初の処理実装
ログイン→パーティとするメンバーの選択とか
軽いストーリー性を暇な時に考える
旅の目的がないとダメかもね
npcのページ作るとか