ユニティちゃんのRPGを作ってみよう22ー敵の攻撃ターンを作成するー

今回から戦闘でのキャラクターの行動処理を作成していきます。

キャラクターの行動処理は最低限の機能に絞って作成していますが、アイテムコマンドを作った時と同じような面倒さと大変さがあります。(^_^;)

覚悟しておいてください。(‘◇’)ゞ

今回はユニティちゃんのRPGの戦闘シーンの敵の攻撃ターンを作成していきたいと思います。

前回は戦闘シーンで使用するキャラクターのスキルデータを作成しました。

ユニティちゃんのRPGを作ってみよう21ー戦闘で使うスキルデータを作成するー
ユニティちゃんのRPGの戦闘シーンで使用するスキルデータを作成してきます。

ユニティちゃんのRPGを作ってみようの他の記事は

ユニティちゃんのRPGを作ってみよう
ユニティちゃんのRPGを徐々に作っていくカテゴリです。

から見ることが出来ます。

今回の機能を作成すると以下のような感じで敵のターンの攻撃が行われます(敵の素早さを味方キャラクターより高くしておく必要あり)。

コマンド式の戦闘では行動をするキャラクターを順番に選び、敵であれば何らかのアルゴリズムに従って行動をし、味方であればそのキャラクターが持っているスキルに応じて選択肢をユーザーが選んで行動をする必要があります。

敵のターンでのアルゴリズム、味方のターンでの選択肢の表示や実行する処理を一遍に作成すると大変なので、今回は敵のターンでのアルゴリズムを作成していきます。

アルゴリズムと言うと難しく感じますが、要は敵にどのように行動させるか?という方法を考えます。

最初の数ターンは直接攻撃のみ→その後、数ターンは魔法を使って行動→敵の仲間の体力が減っていたら回復魔法を使う

というように行動パターンを作成する必要があります。

細かく攻撃パターンを作成すると大変なので、今回は敵が持っているスキルの中からランダムに一つを選び、その行動を行うようにします。

持っているスキルの中からランダムな一つを選ぶだけなので非常に頭の悪い行動になりますね・・・・(^_^;)

スポンサーリンク

戦闘管理をするBattleManagerスクリプトに処理を追加する

戦闘の順番や敵の起こす行動の決定方法、味方キャラクターの選択肢の表示と選択時の処理を行うスクリプトは

ユニティちゃんのRPGを作ってみよう18ー戦闘シーンへ遷移したらキャラクターを配置するー
ユニティちゃんのRPGで戦闘シーンへ遷移したら味方のパーティーステータスと遭遇した敵のパーティーステータスからキャラクターのプレハブを取得しそれをインスタンス化して戦闘位置に配置する機能を作成していきます。

で作成したBattleManagerスクリプトなので、ここに処理を追加していきます(一部変更している可能性があります)。

敵の行動パターンは戦闘用の敵のプレハブ等に別スクリプトとして取り付けて、そこに行動パターンを設定しておいてBattleManagerで敵固有の行動パターンを取得し行動を起こさせる方が敵によって行動パターンを変える事が出来ます。

しかしそれを行うと大変なのでBattleManagerスクリプト内で敵の行動をランダムに選んで実行するという方法にします。

BattleManagerスクリプトは長いので少しずつ記述していきます。

フィールド宣言の追加

まずはフィールドの宣言を追加します。

CommandModeは味方キャラクターのターンの時に設定する現在何をしているか?を表す列挙型で敵のターンの時は使用しませんが、先に作っておきます。

battleDataは戦闘で使用するデータで以前に作成したAssets/RPG/Data/Status/Battle/BattleDataをインスペクタで設定します。

battleBasePositionは以前作成したキャラクターを配置する場所の元となるゲームオブジェクトのBattleBasePositionを設定します。

allCharacterListは戦闘開始時の味方と敵の全てのキャラクターを登録するリストです。

allCharacterInBattleListは現在戦闘に参加している味方と敵のキャラクターのリストです。

allyCharacterInBattleListは現在戦闘に参加している味方キャラクターのリストです。

enemyCharacterInBattleListは現在戦闘に参加している敵のキャラクターのリストです。

これらのリストは戦闘開始時にBattleDataからデータを取得してリストを作成します。

currentAttackOrderは現在ターンが回ってきているキャラクターの番号を入れます。

isChoosingは現在ターンが回ってきている人が行動を選択しているかどうか。

isStartBattleは戦闘が開始しているかどうか

firstWaitingTimeは戦闘が開始してから最初のターンのキャラクターが行動を起こすまでの間隔時間を設定します。

timeToNextCharacterは前のターンのキャラクターの行動が終わってから次のキャラクターが行動出来るまでの間隔時間です。

waitTimeは待ち時間を入れます(waitTimeに設定するのはfirstWaitingTimeかtimeToNextCharacterのどちらかです)。

elapsedTimeは間隔時間までの計測した時間を入れるフィールドです。

battleIsOverは戦闘が終了しているかどうかです。

currentCommandは現在のコマンドモードを入れます。

いっぱいフィールドがあって嫌になりますね・・・・・(^_^;)

Startメソッド

StartメソッドではBattleDataから取得した味方キャラクターと敵キャラクターのデータから戦闘用キャラクターのインスタンス化を行い、インスタンス化したキャラクターゲームオブジェクトをリストに追加しています。

キャラクタープレハブをインスタンス化する機能自体は以前の記事で作成しているので特に問題はないと思いますが、インスタンス化したゲームオブジェクトの名前をキャラクター名を入れるようにしました。

その際に使用しているCharacterBattleScritpは戦闘用のキャラクター全てに取り付ける予定のスクリプトでそのキャラクターの実際の行動はCharacterBattleScriptで行います。

CharacterBattleScriptは後で作成します。

味方キャラクターの場合はallyCharacterInBattleListとallCharacterList、敵キャラクターの場合はenemyCharacterInBattleListとallCharacterListにインスタンス化したゲームオブジェクトを追加しています。

味方キャラクターの場合は死んでいる場合はリストに登録しないようにします。

敵の場合はキャラクター名が被ってしまうので、最初の敵はA、次の敵はBとなるようにします。

それを行っているのが、以下のような処理です。

敵のキャラクター名を取得し、そのキャラクター名の一部がenemyNameListに含まれていなければキャラクター名にAを付けて敵の名前にします。

例えば敵の名前がエルフであれば『エルフA』という名前にします。

敵のキャラクター名が既にenemyNameListに含まれていればenemyNameListにどれだけそのキャラクター名のデータがあるかをenemyNameList.Countで計測し、’A’という文字に足しています。

‘A’はchar型でこれにenemyNameList.Countでカウントした数を足すことで’A’が暗黙的にint型に変換されます。

‘A’はint型で65になります。

‘A’にカウントした数値を足して、(char)でキャストして文字型に戻しています。

enemyNameListに既にエルフが2体登録されていればenemyNameList.Count(enemyName => enemyName == characterName))では2が返されます。

はラムダ式でenemyNameListに登録されている名前をひとつひとつenemyNameという仮の引数に入れ、その引数とcharacterName(インスタンス化する敵のゲームオブジェクトのキャラクター名)と同じ時にCountでカウントされます。

ラムダ式に関しては

C#でデリゲートとラムダ式を使ってみる
C#のデリゲートとラムダ式をUnity付属のMonoDevelopで使用してみます。

を参照してみてください。

‘A’をint型に変換すると65で、それに2を足すと67になりこれをchar型にキャストをすると’C’が得られるので、そのキャラクターのインスタンス化したゲームオブジェクトの名前は『エルフC』となります。

味方キャラクターと敵キャラクタープレハブのインスタンス化とリストに登録が済んだら、戦闘の行動を起こす順番を作成する為にリストを並べ替えます。

allCharacterListのOrderByDescendingメソッドを使ってリストに登録されているゲームオブジェクトのキャラクターの素早さの数値の高い順にソートし、それをToListメソッドでその場でリスト化し再度allCharacterListに入れなおしています。

これで素早さの高い順にallCharacterListが並び替えられました。

現在の戦闘に参加している全キャラクターallCharacterInBattleListは最初はallCharacterListと同じなので、

上のようにallCharacterListからリストを作成しallCharacterInBattleListに入れておきます。

waitTimeには戦闘開始前なのでfirstWaitingTimeを入れておきます。

戦闘開始時にランダムのシードをゲーム開始からの時間をint型にキャストして設定します。

これは戦闘時に使用するランダム計算の種を毎回変更する為です。

Updateメソッド

Updateメソッドに次のターンのキャラクターの設定やキャラクター間の間隔時間の計算処理等を追加します。

Updateメソッドの最初でbattleIsOverがtrue(戦闘終了)だったらreturnでその後の処理をしないようにします。

isStartBattleがtrueの時は戦闘が開始していて、isChoosingがfalseだった時は戦闘中で行動選択中ではない時なので経過時間を計算し、待ち時間を過ぎていなければreturnでその後の処理をしません。

時間が経過していたらisChoosingをtrueにして行動選択中にします。

MakeAttackChoiseメソッドは行動選択処理をするメソッドで引数に今行動するキャラクターゲームオブジェクトを渡します。

currentAttackOrderをインクリメントして次のキャラクターの番にします。

currentAttackOrderがリストの最後になっていたら0を代入して最初のキャラクターに戻します。

isChoosingがtrueの時は味方キャラクターの行動中の処理を行いますが、今回の記事では敵の行動処理だけを作るので今のところ何も処理は記述しません。

戦闘開始していない時はelseにいきます。

戦闘開始前の時間計測がwaitTimeを越えたらwaitTimeにtimeToNextCharacterの時間を設定します。

elapsedTimeにもtimeToNextCharacterを設定し、戦闘開始した時の最初のキャラクターの待ち時間はないようにします。

isStartBattleにtrueを入れ戦闘を開始します。

MakeAttackChoiseメソッド

Updateメソッドで呼んでいるMakeAttackChoiseメソッドを作成します。

引数で行動をするキャラクターのゲームオブジェクトを受け取ります。

受け取ったキャラクターのゲームオブジェクトからCharacterBattleScriptスクリプトを取得し、GetCharacterStatusメソッドでCharacterStatusデータを取得します。

CharacterStatusをas EnemyStatusでキャストしてnullでなければキャストが可能なので敵キャラクターの行動だと判断します。

ゲームオブジェクトの名前+攻撃という表示をコンソールに表示し、EnemyAttackメソッドにキャラクターのゲームオブジェクトを渡して呼び出します。

EnemyStatusにキャスト出来ないCharacterStatusは味方キャラクターに設定したAllyStatusなのでゲームオブジェクトの名前+攻撃という表示をしてAllyAttackメソッドにゲームオブジェクトを引数として渡して呼び出します。

敵キャラクターにはEnemyStatus、味方キャラクターにはAllyStatusスクリプトを取り付けていて、この二つは元々CharacterStatusクラスを継承して作成しているので、

下記のようにキャスト出来るかどうかで敵キャラクターなのか味方キャラクターなのかを判断し、該当するメソッドを呼び出すという処理にしています。

別のわかりやすい判定方法もあるかと思いますが、今回はこの方法にしました。

下のような感じでよかったのかも・・・・(^_^;)

まぁよしとしましょう・・・・。

EnemyAttackメソッドはこの後作成しますが、AllyAttackメソッドは味方キャラクターの攻撃処理なので次回以降作成していきます。

EnemyAttackメソッド

敵の行動処理を行うEnemyAttackメソッドを作成します。

最初に引数として受け取ったキャラクターのゲームオブジェクトからCharacterBattleScriptとそのCharacterBattleScriptのGetCharacterStatusメソッドを使ってCharacterStatusを取得します。

characterStatusのGetSkillListメソッドを使ってそのキャラクターが持っているスキルを取得し、スキルがひとつもなければreturnを使ってそれ以降の処理はさせないようにします。

次にキャラクターのAnimatorコンポーネントを取得し、アニメーションパラメータ―のGuardがtrueになっていたら、CharacterBattleScriptのUnlockGuardメソッドを呼んで防御状態を解除します。

これはこのキャラクターが前のターンで防御をしていた場合に次のターンが来たら防御を解除する為です。

次にRandam.valueで0~1の間のfloat値を取得しキャラクターが持っているスキルの数と掛けます。

これを(int)でキャストし整数値に変換しスキル数分のランダム値が得られるようにします。

その数値のスキルを取得しnowSkillに入れておきます。

nowSkill.GetSkillType()で選んだスキルのスキルタイプを取得し、そのスキルタイプがどのタイプであるかによって処理を分けます。

Skill.Type.DirectAttackだった時は直接攻撃なので、攻撃をする相手を味方キャラクター(allyCharacterInBattleList)の中からランダムに選びます。

攻撃相手を選んだら攻撃をするキャラクターのcharacterBattleScriptのChooseAttackOptionsメソッドに引数を指定して渡します。

第1引数はCharacterBattleScriptで定義したそのキャラクターが取った行動を示す列挙型を渡し、第2引数で攻撃相手、第3引数は使用したスキルです。

Skill.Type.MagicAttackだった時は魔法を使用するキャラクターのMPが使用する魔法のMP以上の時は魔法攻撃を行い、MPが足りない時は直接攻撃にします。

nowSkillはSkill型なので、(Magic)でキャストしてGetAmountToUseMagicPointsメソッドで使用するMPの量を取得する必要があります。

MPが足りない時に直接攻撃をさせますが、その時に注意が必要になります。

ChooseAttackOptionsメソッドの第3引数にnowSkillを渡してはいけないという事です。

nowSkillには元々MagicAttack系のスキルが入っているので、ここではDirectAttackのスキルを渡さなくてはいけません。

そこでそのキャラクターが持っているスキルの中からDirectAttackに該当するスキルを探してそれを渡しています(敵キャラクターはDirectAttackのスキルを必ず持っている必要があります)。

Skill.Type.RecoveryMagicの時も同様で、使用する相手をenemyCharacterInBattleListの中から探すことになります。

Skill.Type.Guardの時は防御をした時で、そのキャラクターのCharacterBattleScriptのGuardメソッドを呼びます。

防御の場合はアニメーションパラメータがbool型でそのキャラクターの次のターンになるまではずっと防御をしたままにするのでChangeNextCharaメソッドを呼んで直ぐに次のキャラクターにターンを回します。

ChangeNextCharaメソッド

次のキャラクターにターンを回すChangeNextCharaメソッドを作成します。

ChangeNextCharaメソッドが呼ばれたらisChoosingをfalseにし次のキャラクターにターンが回るようにします。

キャラクターが死んだときの処理メソッド等

キャラクターが死んだときに該当するリストからそのキャラクターを削除する処理を記述します。

DeleteAllCharacterInBattleListメソッド

キャラクターが死んだときに戦闘に参加している全キャラクターからそのキャラクターを削除する必要があるのでDeleteAllCharacterInBattleListメソッドを作成します。

引数で受け取ったリストから削除するゲームオブジェクトがリスト内で何番目かをIndexOfで求めてdeleteObjNumに入れます。

その後リストからそのゲームオブジェクトを削除し、deleteObjNumがcurrentAttackOrderより小さい時はcurrentAttackOrderをデクリメントして1減らします。

これはcurrentAttackOrderの番号はそのゲームオブジェクトを削除する前のリストの何番目かを表しているので、次のターンのキャラクターの前のリストのゲームオブジェクトを削除した場合はcurrentAttackOrderを1減らす必要がある為です。

currentAttackOrderが戦闘に参加しているキャラクターリストの数以上になっていたら最初のキャラクターにターンを回します。

例えば以下のように4人のキャラクターが戦闘に参加しているとして、現在currentAttackOrderが2だとします。

ユニティちゃんRPGのターン制のイメージ図

自身の前の順番のキャラクターを倒すと、リストからそのキャラクターゲームオブジェクトが削除されるのでcurrentAttackOrderの2は自身の前のキャラクターを指してしまいますのでcurrentAttackOrderを1減らしてまた自身を指すように調整します。

ユニティちゃんRPGで前の順番のキャラを倒した時

自身の後のキャラクターを倒した場合で、倒したキャラクターがリストの最後である場合にUpdateメソッドで次のターンに行くとこの時点ではcurrentAttackOrderが3になっているので範囲外のリストを参照してエラーになってしまいます。

なので、リストを削除した段階でcurrentAttackOrderがリストの数を超えていたら0にして最初のキャラクターのターンにしています。

ユニティちゃんRPGで最後の順番のキャラクターを倒した時

もっと簡単な処理で出来そうですが・・・・(´Д`)

DeleteAllyCharacterInBattleListメソッド

味方キャラクターが死んだ時はallyCharacterInBattleListから削除します。

allyCharacterInBattleListに登録されている数が0になったら味方キャラクターがいなくなったので全滅しbattleIsOverをtrueにして戦闘処理を終えます。

DeleteEnemyCharacterInBattleListメソッド

敵キャラクターが死んだときはenemyCharacterInBattleListから削除します。

やっていることは味方キャラクターの時と同じです。

味方キャラクターが全滅、または敵キャラクターが全滅した時は戦闘を終了させますが、その処理はまた今度作成したいと思います。

BattleManagerゲームオブジェクトのインスペクタは以下のように設定します。

ユニティちゃんRPGのBattleManagerのインスペクタの設定

firstWaitingTimeは3にし戦闘シーンに入ってから3秒間経ってから最初のキャラクターの行動を始めます。

timeToNextCharacerは1にし前のキャラクターの行動が終わってから1秒経ったら次のキャラクターが行動するようにします。

キャラクター個々の戦闘処理スクリプトCharacterBattleScriptの作成

BattleManagerスクリプトでターンが回ってきたキャラクターの選択とその行動の決定をしました。

実際の行動の処理は戦闘用のキャラクターのプレハブにCharacterBattleScriptスクリプトを取り付けそこで行います。

スクリプトが長いので分割して説明します。

クラス定義とフィールド宣言

まずはクラスの定義とフィールドを作成していきます。

BattleStateはSkillのスキルタイプと同じような表記をしていますが、これはこのキャラクターが現在どんな行動をしているかを表す列挙型として使います。

自分のターンでない時はBattleState.Idleにします。

battleManagerはBattleManagerスクリプト、battleStatusScriptは戦闘シーンで味方キャラクターのステータスを表示しているスクリプトを入れます。

characterStatusにはこのスクリプト(CharacterBattleScript)を取り付けたキャラクターのCharacterStatusデータを設定します。

animatorはこのキャラクターのAnimatorコンポーネント、battleStateはこのキャラクターの状態を表す列挙型のBattleStateを保持します。

キャラクターのステータスをコピーして使用する必要がある?

さて、フィールド宣言を見て少し不思議に思った方もいるかもしれません。

それは以下のようなフィールドを用意しているからです。

キャラクターにはCharacterStatusというキャラクターのステータスを設定していてそこでHPやMP、痺れ状態か、毒状態か等の情報の保持と取得が出来るはずです。

なぜCharacterBattleScriptで改めて同じフィールドを宣言しているかというと、CharacterStatusのデータが一つだと同じキャラクターが何体か出現した時に困るからです。

例えば『エルフ』が敵として2体出現し『エルフA』『エルフB』としそれぞれEnemyStatusスクリプト(CharacterStatusを継承して作った)から作成したElfデータを使うとします。

Elfデータでは最大HPや現在のHP等のデータを持っていますが、『エルフA』に攻撃を与えてElfデータを書き換えると『エルフB』のCharacterStatusのGetHpメソッドを呼び出した時にElfデータからデータを取得する為既にHPが減った状態になってしまいます。

『エルフA』はElfA、『エルフB』はElfBというデータを予め作っておいて個別に設定すればいいですが、膨大な量のデータを作成する必要が出てくるので、今回はElfデータは初期データとして使い、そこから一旦CharacterBattleScriptにHPやMPのデータをコピーし、戦闘中はCharacterBattleScriptのHPやMPを使うようにします。

こうすることで『エルフA』と『エルフB』がデータを共有する必要がなくなります。

味方キャラクターの場合は同一キャラクターはいないのでそのまま個々のデータを書き換えて使用してもいいんですが、敵キャラクターと合わせてCharacterBattleScriptにデータをコピーして使う事にします。

キャラクターの攻撃力等は同じなのでCharacterStatusからデータを取得します。

戦闘終了時にCharacterBattleScriptのデータをUnityChanStatusデータやYujiStatusデータに上書きをするようにします(この処理は次の記事以降作成します)。

auxiliaryAgility、auxiliaryPower、auxiliaryStrikingStrengthは戦闘中に上がった攻撃力や防御力等を入れます。

currentSkillは選択したスキル

currentTargetは攻撃等の相手のゲームオブジェクト

currentItemは使用したアイテム

targetCharacterBattleScriptは攻撃等の相手のCharacterBattleScript

targetCharacterStatusは攻撃などの相手のCharacterStatus

を保持します。

なぜこれらのフィールドを用意したかというと、攻撃をしたキャラクターが攻撃や魔法のアニメーションを終了した後に攻撃対象のダメージアニメーションやダメージエフェクトの表示、ダメージを減らす処理等を行いたいからです。

なので攻撃者のアニメーションが終了するまで攻撃相手の情報を保持し、アニメーションが終わったらその情報を使ってダメージ処理を行います。

isDoneAnimationはこのキャラクターのターンの行動アニメーションが終了したかどうかを表すフラグです。

isDeadはこのキャラクターが死んでいるかどうかを表すフラグです。

Startメソッド

Startメソッドに処理を記述していきます。

インスペクタで設定したそのキャラクターのCharacterStatusデータからHP、MP、痺れ、毒の状態を取得しフィールドに保持します。

battleManagerはBattleManagerゲームオブジェクトを探し、そこからGetComponentでBattleManagerスクリプトを取得します。

battleStatusScriptも同じように取得します。

戦闘開始時に既にそのキャラクターのHPが0以下であれば死んでいるのでanimator.CrossFadeを使ってアニメーションでDead状態で始まるようにしています。

第1引数にAnimatorControllerの状態を指定し、第2引数で遷移の期間、第3引数でクロスフェードが発生するレイヤーを指定し、第4引数で状態の時間を指定します。

CrossFadeを使うとAnimatorControllerで状態を繋げていなくても遷移させることが出来ます。

また単純に

とすると、戦闘開始時に死ぬアニメーションが再生されてしまいます。

CrossFadeメソッドを使用すると第4引数でその状態の正規化された1fの位置、つまり1回のアニメーションの再生の最後の部分の死んで倒れた状態で戦闘が始まります。

Updateメソッド

Updateメソッドに処理を追加していきます。

Updateメソッドで行う事は自身のターンになったらその行動のアニメーションが終了するのを待って終了したら攻撃等の対象にエフェクトを表示したり対象のHPを減らしたりといった処理をします。

isDeadがtrueだったら既にこのキャラクターは死んでいるのでreturnでその後の処理はしません。

battleStateがBattleState.Idleであればそのキャラクターのターンではないのでreturnでその後の処理はしません。

isDoneAnimationがfalseであればアニメーションが終了していないのでreturnでその後の処理はしません。

行動がBattleState.DirectAttackであればShowEffectOnTheTargetメソッドを呼んで攻撃対象にエフェクトを表示します。

DirectAttackメソッドを呼んで攻撃処理をします。

CheckIncreaseAttackPowerメソッドで攻撃力アップ魔法のチェック、CheckIncreaseStrikingStrengthで防御力アップ魔法のチェックを行います。

これは魔法で攻撃力や防御力が上がっている時に指定したターン経過したら効果を消すためのチェックをするメソッドです。

行動がBattleState.MagicAttackの時はMagicAttackメソッドに変えているだけでDirectAttackと同じです。

行動が回復魔法、状態回復の時はUseMagicメソッドに変更しているだけです。

行動がBattleState.IncreaseAttackPowerMagicの時は攻撃力を上げる対象のゲームオブジェクトと自身が同じだったらCheckIncreaseAttackPowerメソッドは呼び出さないようにします。

これは自分自身に攻撃力アップ魔法をかけた時にそのターン内で1回ターンを過ごしたと計測しない為です。

行動がBattleState.IncreaseDefencePowerMagicの時もやっている事は同じでCheckIncreaseStrikingStrengthを呼び出さないようにしています。

行動がアイテムを使用した場合はShowEffectOnTheTargetを呼び出していませんが、ItemデータでSkillReceivingSideEffectにエフェクトを設定した場合は呼び出すとエフェクトを表示出来ます。

アイテム個々でエフェクトを変えたい場合はItemというスキル一つではなく新たにSkillスクリプトを継承してMagicのように個々のアイテムスクリプトを作りエフェクトを設定出来るようにするといいかもしれません。

行動が終わったら攻撃等の対象情報を初期化しそのキャラクターの状態をBattleState.Idleに戻し、BattleManagerスクリプトのChangeNextCharaメソッドを呼んで次のキャラクターにターンを回します。

そしてisDoneAnimationをfalseにします。

フィールドのセッター、ゲッターメソッド

CharacterBattleScriptに用意したHPやMP等のデータを設定したり取得するメソッドを作成します。

SetHpやSetMpメソッドではそのキャラクターのHPやMPの最大値と受け取った値をMathf.Minメソッドで最小値の方を取得し、それをMathf.Maxメソッドで0と比較して大きい方を返します。

最初のMathf.Minでは値を回復した時に最大HPやMPを超えないようにし、次のMathf.Maxではダメージを受けて減らした時に0より下の数値にならないようにする為です。

HPが0以下になったらDeadメソッドを呼び出して死んだ後の処理をします。

選んだ選択肢の処理メソッドChooseAttackOptionsの作成

BattleManagerメソッドで敵の行動を選んだらそのキャラクターのCharacterBattleScriptのChooseAttackOptionsメソッドを呼び出していました。

ChooseAttackOptionsメソッドは選んだ行動を処理する為のメソッドです。

ChooseAttackOptionsメソッドの引数でSkill型とItem型を取れるようにしていますが、Item型の引数は必要ない事もあるので引数が渡ってこなかった場合はnullで初期化するようにしています。

メソッドの最初で引数で受け取った攻撃等の対象のゲームオブジェクトから情報を取得しフィールドに保持しておきます。

行動がBattleState.DirectAttackの場合はアニメーションパラメータのDirectAttackをトリガーして直接攻撃のアニメーションを再生させます。

行動が魔法系の時はアニメーションパラメータのMagicAttackをトリガーして魔法攻撃のアニメーションを再生させます。

SetMpメソッドを呼んで魔法使用者のMPを減らします。

魔法を使用したのが味方キャラクターだった場合は魔法使用者のBattleStatusScriptのUpdateStatusメソッドを呼んでステータス表示を更新します。

その後に引数で受け取ったskillの攻撃者側のエフェクトをインスタンス化し表示します。

インスタンス化する場所は魔法使用者の位置で、エフェクトの角度はエフェクト自身の元の角度を使います。

行動がアイテム系だった時はcurrentItemにitemを入れアニメーションパラメータのUseItemをトリガーします。

今回はアイテム系は味方キャラクターのみ使う事が出来るようになっています。

敵キャラクターもアイテムを使えるようにする場合はそれ用に追加の処理を記述していく必要があります。

ChooseAttackOptionsメソッドは引数として受け取った情報をフィールドに保持し、このキャラクターの状態を変更し、受け取った行動のエフェクトの表示等をしています。

対象のゲームオブジェクトにエフェクトを表示するShowEffectOnTheTargetメソッド

攻撃等をした時に攻撃対象のゲームオブジェクトの近くにエフェクトを表示するShowEffectOnTheTargetメソッドを作成します。

currentSkillが使用したスキルなのでそこから相手に表示するエフェクトを取得しインスタンス化します。

直接攻撃を受けた対象のダメージ処理をするDirectAttackメソッド

直接攻撃を受けた対象のダメージ処理をするDirectAttackメソッドを作成します。

攻撃を受けたゲームオブジェクトはcurrentTargetなのでそこからAnimatorコンポーネントを取得し、アニメーションパラメータのDamageをトリガーして攻撃を受けたキャラクターのダメージアニメーションを再生させます。

攻撃を受けたキャラクターが味方キャラクターであればそのキャラクターのCharacterStatusをAllyStatusにキャストしてcastedTargetStatusに入れます。

castedTargetStatusからキャラクターの防御力や装備している防具、魔法で補正されている防御力から全体の防御力を計算しtargetDefencePowerに入れます。

上の処理は分かり辛いですが、castedTargetStatus.GetEquipArmorメソッドで装備している防具を取得し、それがnullだったら防具を装備していないので??の後の0を返し、

nullでなければGetAmountメソッドの値を取得し返します。

ダメージの計算ではMathf.Maxメソッドを使って0と計算したダメージを比較し大きい方をdamageに入れます。

これはtargetDefencePowerの値の方が大きかった時にdamageにマイナスの値が入ってしまう事を避ける為です。

ダメージの計算が終了したら攻撃対象のCharacterBattleScriptのSetHpメソッドを呼んでHPを減らします。

また味方キャラクターが攻撃を受けた場合はBattleStatusScriptのUpdateメソッドを呼び出してステータス表示を更新します。

攻撃を受けたのが敵キャラクターの場合は防具を装備していないので防御力のみでダメージを計算します。

やっていることは味方キャラクターの時と同じです。

魔法攻撃を受けた対象のダメージ処理をするMagicAttack

魔法攻撃を受けた対象のダメージ処理をするMagicAttackメソッドを作成します。

やっている事はDirectAttackメソッドとほとんど同じなので説明は省きます。

魔法攻撃以外の魔法を受けた時の処理をするUseMagicメソッド

魔法攻撃以外の魔法を受けた時の処理をするUseMagicメソッドを作成します。

魔法のタイプがUseMagicの時は対象のCharacterBattleScriptのSetHpを呼び出してHPを回復させます。

IncreaseAttackPowerMagicの時は攻撃力の補正値を上げます。

IncreaseDefencePowerMagicの時は防御力の補正値を上げます。

NumbnessRecoveryMagicの時は痺れをなくします。

PoisonnouRecoveryMagicの時は毒をなくします。

アイテムを受けた時の処理を行うUseItemメソッド

アイテムを使用された時の処理を行うUseItemメソッドを作成します。

今回はアイテムを使用するのは味方キャラクターだけを想定しています。

この記事では敵キャラクターの処理だけを作っていっていますが、UseItemメソッドで行っていることもUseMagicメソッド等とほとんど変わらないのでここで作ってしまいます。

アイテムを使用されたキャラクターのアニメーション状態を作っていなかったのでアイテムを受けたキャラクターはダメージを受けた時と同じアニメーションを再生する事にします。

アイテムを使用するのは味方キャラクターだけだと想定しているので、CharacterStatusをAllyStatusにキャストしてSetItemNumメソッドを使って使用したアイテムの数を一つ減らします。

その後は使用したアイテムの種類を判別し、対応する処理をしているだけです。

行っていることはHPやMPを回復したり、状態を回復しているだけなので問題はないと思います。

アイテムを減らした後にそのアイテムが0になったらAllyStatusのItemDictionaryからそのアイテム自体を削除します。

防御した時のGuardメソッドと解除した時のUnlockGuardメソッド

キャラクターが防御した時のGuardメソッドと防御を解除した時のUnlockGuardメソッドを作成します。

BattleManagerでターンが来たキャラクターの行動を選択しChooseAttackOptionsメソッドを呼び出してそのキャラクターの状態を変更していますが、防御をする時だけはChooseAttackOptionsメソッドを呼び出さず直接Guardメソッドを呼び出しています。

なのでUpdateメソッド内でbattleStateを比較しても防御の場合はBattleState.Idleの状態なのでそれ以降の処理はしていません。

そこでGuardメソッドでCheckIncreaseAttackPowerメソッドとCheckIncreaseStrikingStrengthメソッドを呼んで補正値のチェックをしておきます。

アニメーションパラメータのGuardをtrueにして防御アニメーションを再生させます。

UnlockGuardメソッドは防御をしたキャラクターのターンが終了し、他のキャラクターのターンになってから再び防御をしているキャラクターに戻った時にBattleManagerスクリプトで呼び出しています。

ここではアニメーションパラメータのGuardをfalseにし、補正値を元に戻しています。

キャラクターが死んだときの処理をするDeadメソッド

キャラクターが死んだときの処理をするDeadメソッドを作成します。

AnimatorControllerのアニメーションパラメータのDeadをトリガーし死んだときのアニメーションを再生します。

死んだのでBattleManagerのDeleteAllCharacterInBattleListから自身のゲームオブジェクトを削除します。

その後、このキャラクターが味方キャラクターか敵キャラクターかを判別し、該当するリストから自身のゲームオブジェクトを削除します。

isDeadをtrueにし、UpdateメソッドのisDeadで判定している以降の処理を実行しないようにします。

攻撃補正チェックをするCheckIncreaseAttackPowerメソッドと防御補正チェックをするCheckIncreaseStrikingStrengthメソッド

攻撃補正チェックをするCheckIncreaseAttackPowerメソッドと防御補正チェックをするCheckIncreaseStrikingStrengthメソッドを作成します。

これらのメソッドは補正魔法が使われてから一定のターンが経過したら補正を元に戻す為の処理をしています。

これで敵の行動処理を記述したCharacterBattleScriptが出来たので、

Assets/RPG/Prefabs/Characters/Enemyフォルダの敵のプレハブであるElfPrefab、GoblinPrefab、GoblinWarrirPrefab、
Assets/RPG/Prefabs/Characters/Allyフォルダの味方の戦闘シーン用のプレハブであるBattleUnityChan、BattleYuji、

にCharacterBattleScriptを設定し、インスペクタのcharacterStatusにそれぞれのキャラクターのステータスデータを設定します。

例えばElfPrefabであれば、以下のようにcharacterStatusにElfデータのアセットファイルを設定します。

ユニティちゃんRPGのElfPrefabに取り付けたCharacterBattleScriptのインスペクタの設定

アニメーションが終了したことを知らせるビヘイビアの作成

ここまでで敵の行動選択と実際の行動処理の作成がほとんど作成出来ました。

しかし実際に敵に行動をさせてみると敵がアニメーションをした後の処理が実行されません。

それは敵のアニメーションが終了したかどうかを示しているisDoneAnimationがずっとfalseのままだからです。

アニメーションが終了したらそのキャラクターのCharacterBattleScriptのisDoneAnimationをtrueにし、Updateメソッドの処理を実行するようにしなければいけません。

そこで戦闘で使用しているキャラクターのAnimatorControllerにビヘイビアを取り付けてアニメーションが終了したらそのキャラクターのCharacterBattleScriptのisDoneAnimationをtrueにします。

そこでAnimatorControllerの状態にEndStateというビヘイビアスクリプトを取り付けてアニメーションが終了したらそのキャラクターのCharacterBattleScriptのisDoneAnimationをtrueにすることにします。

Assets/RPG/Animators/AllyフォルダのBattleUnityChanアニメーターコントローラーを選択し、AnimatorタブのDirectAttack状態を選択し、インスペクタのAdd Behaviourボタンを押してNew Scriptを選択し、名前をEndStateと入力します。

BattleUnityChanのDirectAttackにEndStateビヘイビアを取り付ける

AssetsフォルダにEndStateビヘイビアスクリプトが出来ると思うので、Assets/RPG/Scripts/Battleフォルダへ移動させておきます。

EndStateビヘイビアスクリプトには既にメソッドが定義されているのでOnStateEnterメソッドとOnStateExitメソッドのコメントを外しておきます。

OnStateEnterメソッドはAnimatorControllerのEndStateビヘイビアスクリプトが設定された状態に入った時に呼ばれます。

そこでbattleManagerとCharacterBattleScriptが設定されていなければ取得します。

OnStateExitメソッドはEndStateが設定された状態から出る時に呼ばれるので、ここでそのキャラクターのCharacterBattleScriptのSetIsDoneAnimationメソッドを呼び出してisDoneAnimationをtrueにします。

SetIsDoneAnimationメソッドを呼び出すのはアニメーションの状態がDirectAttack、MagicAttack、UseItemの時に行います。

これで行動を起こしたキャラクターのアニメーションが終わってその状態から抜ける時にisDoneAnimationがtrueになり、次のキャラクターへとターンを回します。

これで機能が出来ました。

終わりに

敵キャラの行動処理が出来上がりましたが非常にスクリプトが長いし分かり辛いですね。

ただキャラクターの行動処理をさせるCharacterBattleScriptは味方のキャラクターでも同じなので味方キャラクターで新たに追加する必要はありません。

味方キャラクターの行動を選択する処理等はBattleManagerスクリプトに追加していくという感じになります。

行動処理の後にDebug.Logを使って情報をコンソールに表示していますが、場合によっては情報の表示順が前後してしまいます。

この情報はコンソールで確認する為に表示しているだけなので、次回以降でUIに情報を表示する機能を作っていく予定なのでそれまではあまり気にせずに置いておいてください。

次回は味方キャラクターの行動を選択する機能を作成していきます。

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています

コメント

タイトルとURLをコピーしました