今回はUnityのゲームでボス等の巨大な敵を作成し、主人公を攻撃するようにしたいと思います。
新たな記事として作成しましたが、基本的には通常の敵が主人公を攻撃する機能と同じような感じで作成していきます。
ただまったく同じに作成するといくつかの問題点が出てきますので、そこは巨神兵用の設定を施すようにします。
この機能を作成すると、
上のような感じのものが出来上がります。
通常攻撃の当たり判定が狭いのでそのまま攻撃しても主人公には当たりません。(*‘∀‘)
いろいろ修正すべき点は残りますが、巨大な敵を作成する時の一つのサンプルとして参考にして頂ければと思います。
主人公キャラクターの作成
今回使用する主人公キャラクターは
の記事の「衝撃波と衝突するキャラクターの作成」の項目で作成したキャラクターを使います。
下位項目の「衝撃波との衝突を検知するスクリプト」で作成するCollisionShockwaveスクリプトは記事内で作成する物理的な衝撃波でダメージを負う為に必要ですが、姉妹記事に当たるパーティクルで作る衝撃波を使う場合は必要ありません。
何にしろ巨大な敵が発生させる衝撃波を以下に示す2つの記事のどちらかで作成しておく必要があります。
上の二つの記事は今回の記事で使用する衝撃波を作る為に作った記事です。(^^)/
作成した主人公キャラクターのインスペクタのTagにはPlayer、LayerにはCharaという新しいレイヤーを作成し設定しておきます。
巨大な敵の作成
ここから今回のメインである巨大な敵の作成を行っていきます。
敵のモデル、アニメーションはアセットストアの無料のものを使用する事にします。
アセットストアで『creature cave troll』で検索して出てくる無料のモデルをダウンロードします。
インポートしたファイルの中にはモデルの他にトロールの歩きや攻撃等のアニメーションもあるのでこちらを今回の記事で使わせてもらう事にします。
トロールのRigはHumanoidで設定はされておらずGenericなので、他のHumanoid型のアニメーションを適用する事は出来ません。
Assets→cave_troll→prefabsの中にあるtrollプレハブをヒエラルキー上にドラッグ&ドロップし、名前をGiantTrollと変更します。
GiantTrollのインスペクタのLayerにはEnemyという新しいレイヤーを作成し設定しますが、設定する時にChange Layerという新しいウインドウが表示されます。
今回Layerを変更するのは大本のGiantTrollだけにしたいので、No this object onlyボタンを押します。
なぜ大本のGiantTrollのオブジェクトだけをEnemyレイヤーにしたかというと、GiantTrollに設定したCharacterControllerのコライダとGiantTrollの子要素のオブジェクトにこの後設定するコライダの衝突設定を別にしたい為です。
それについては後で説明します。
トロールを巨大化する
次にインポートしたトロールのモデルの大きさはスタンダードアセットのEthan等と比べてもそんなに大きくはありません。
そこでトロールを大きくします。
本来であればモデルのインポート設定のScale Factorの数値を調整して、モデルの大きさを調整したいところですが、スタンダードアセットのEthanのようにモデルの子要素のオブジェクトのTransformのScaleが1となっていません。
なのでScale Factorで変更してしまうと、モデルのバランスが崩れてしまうので、今回はヒエラルキー上に配置した大本のGiantTrollのTransformのScaleのXYZを2に変更し、全体の大きさを大きくすることにします。
GiantTrollを選択してインスペクタのTransformのScaleのX、Y、Zを2にします。
主人公キャラクターであるEthanと比較すると、
上のような感じになり、巨大なトロールになりました。
トロールのAnimatorControllerの作成
次にトロールのアニメーターコントローラーを作成します。
Assetsエリア内で右クリック→Create→Animator Controllerを選択し、名前をGiantとします。
アニメーションパラメータにはFloat型のWalkSpeed、Trigger型のAttack、Bool型のChase、Trigger型のShockwaveAttackを作成します。
状態にはIdle(突っ立っている状態)、Walk(見回りをしている状態)、Chase(キャラクターを追いかけている状態)、Attack(通常攻撃)、ShockwaveAttack(衝撃波込みの攻撃)を作成します。
状態と遷移は以下のように作成します。
Idle→WalkはWalkSpeedがGreaterで0.1
Walk→IdleはWalkSpeedがLessで0.1
Idle→Chase、Walk→ChaseはChaseがtrue
Chase→IdleはChaseがfalse
Idle→Attack、Walk→AttackはAttackがトリガーされた時
Idle→ShockwaveAttack、Walk→ShockwaveAttackはShockwaveAttackがトリガーされた時
ここまでの全ての遷移ではHas Exit Timeのチェックを外し、すぐに遷移するようにします。
Attack→Idle、ShockwaveAttack→IdleはHas Exit Timeにチェックを入れ、アニメーションの終了時に遷移するようにします。
個々の遷移時のアニメーションのブレンドは適当に設定してください。
アニメーションブレンドに関しては、
を参照してください。
アニメーションパラメータでBool型のChaseを作っていますが、WalkSpeedの条件を使ってChaseの条件にする事も出来ると思いますが、わかりやすくBool型のChaseを作りました。
アニメーターコントローラーのGiantが出来たらGiantTrollのAnimatorにドラッグ&ドロップして設定します。
さらにインスペクタのAdd ComponentからPhysics→CharacterControllerを選択し、コライダのサイズを調整します。
上のような感じで設定しました。
レイヤーの衝突設定を行う
作成したトロールはCharacterControllerを使って移動させることにしました。
しかし、巨大なトロールの当たり判定をCharacterControllerのコライダにしてしまうと主人公キャラクターがトロールの足の間を通れないことになってしまいます。
また、トロールを移動出来るようにすると、
上のように主人公キャラクターのEthanの上を歩いて登ってしまうようになります。
これはCharacterControllerの坂等の段差を上れるようにする設定であるSlop LimitやStep Offsetを小さくして主人公キャラクターのコライダを昇れないようにして対処する事も出来ますが、これだと地面のちょっとした坂等も上れなくなります。
なので、今回はPhysicsManagerで敵のCharacterControllerのコライダのオブジェクトに設定したEnemyレイヤーと主人公のCharacterControllerのコライダのオブジェクトに設定したCharaレイヤーの衝突の設定を変更し、お互いに衝突しないようにします。
UnityのメニューからEdit→Project Settings→Physicsを選択します。
上のようにGiantTrollに設定したEnemyレイヤーと主人公キャラに設定したCharaレイヤーのLayer Collision Matrixでチェックを外し、お互いのコライダが衝突しないようにします。
衝突しない設定にしたことで起きる問題の対処
トロールと主人公キャラのCharacterControllerのコライダがお互いに衝突しなくなりトロールが主人公の頭の上を歩いていくということはなくなりました。
しかしお互いに衝突しないようになってしまいます。
そこでトロールには別途コライダをボーンの子要素に配置し、主人公のコライダと衝突出来るようにしこれを解消します。
つまりトロールのCharacterControllerのコライダは移動に使い、キャラクターとの衝突判定のコライダは別途用意するということになります。
GiantTrollのボーンの子要素にCreate Emptyで空のゲームオブジェクトを作成しCollisionColliderという名前にして両足、胴体のボーンの子要素に作ります。
それぞれのCollisionColliderには見た目に合わせたコライダ(今回はCapsuleCollider)をAdd Component→Physics→Capsule Colliderで取り付けます。
コライダは物理的に衝突するようにする為、Is Triggerのチェックはしません。
また今回両足に取り付けたコライダは実際のトロールの動きに合わせたコライダにはなっていないので、見た目とコライダの動きが合うようなボーンの子要素にコライダを取り付けてください。
キャラクターに細かくコライダを取り付ける方法は、
上を参照してください。
Capsule ColliderのRadiusやHeightが非常に大きい値になっていますが、これはGiantTrollの子要素のオブジェクトのScaleがルートであるGiantTrollから変化している為です。
実際に取り付けたコライダは、
上のようになりました。
両足と胴体にコライダを取り付けただけなのでそれ以外の部分はすり抜けてしまいます。
ジャンプ力がある主人公キャラクターであれば両足のコライダの上に乗っかってしまうこともありえますので、それに応じてコライダを増やしたりする必要があります。
作成した3つのCollisionColliderのLayerはDefaultのままなので、Charaを設定した主人公キャラのCharacterControllerのコライダと衝突する事になります。
トロールの行動処理スクリプトTrollScript2の作成
次にトロールの行動処理スクリプトTrollScript2を作成していきます。
名前がTrollScript2となっているのはわたくしの都合でなっているだけなので適当に変更して作成し、GiantTrollゲームオブジェクトに取り付けてください。
スクリプトが長いので少しづつ解説します。
宣言部分とStartメソッド
まずはスクリプトのフィールド部分とStartメソッドの部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | public class TrollScript2 : MonoBehaviour { public enum TrollState { idle, patrol, chase, attack, shockwaveAttack } private CharacterController characterController; private Animator animator; // トロールが登場した最初の位置 private Vector3 defaultPos; // トロールの状態 private TrollState trollState = TrollState.idle; // 目的地 private Vector3 destination; // 移動範囲 [SerializeField] private float movementRange = 20f; // 移動速度 private Vector3 velocity = Vector3.zero; // 歩くスピード [SerializeField] private float walkSpeed = 0.5f; // 追いかけるスピード [SerializeField] private float chaseSpeed = 1f; // 向きを回転する速さ [SerializeField] private float rotateSpeed = 2f; // idle状態の経過時間 private float elapsedTimeOfIdleState = 0f; // idle状態で留まる時間 [SerializeField] private float timeToStayInIdle = 3f; // 攻撃対象のTransform private Transform attackTargetTransform; // 攻撃時の対象の位置 private Vector3 attackTargetPos; // Use this for initialization void Start () { characterController = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); defaultPos = transform.position; SetRandomDestination(); } |
TrollStateはトロールの状態を表しています。
defaultPosはトロールが最初に出現した位置を記憶しておく為のフィールドです。最初に登場した位置から一定以上は離れないようにする為にここを移動の基点にします。
movementRangeはトロールが見回りをする範囲の最大距離を入れます。
walkSpeedは見回り時の移動スピード、chaseSpeedは主人公キャラを追いかける時のスピードを設定します。
rotateSpeedは目的地を向く時のキャラクターの回転スピードです。
elapsedTimeOfIdleStateはIdle状態になってからの経過時間を入れます。
timeToStayInIdleはIdle状態から見回り状態になるまでの時間を設定します。
attackTargetTransformは追いかける対象のTransformを入れます。
attackTargetPosは攻撃時に対象がいた位置を記憶しておきます。
Startメソッドではコンポーネントの取得、登場位置に記録、SetRandomDestinationメソッドで目的地の設定をしています。
SetRandomDestinationメソッドは後で作成します。
Updateメソッド
Updateメソッドではトロールの状態に応じて処理を分けています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Update is called once per frame void Update () { // トロールの状態によって処理を変える if (trollState == TrollState.idle) { Idle(); } else if (trollState == TrollState.patrol) { Patrol(); } else if (trollState == TrollState.chase) { Chase(); } else if (trollState == TrollState.attack) { Attack(); } else if(trollState == TrollState.shockwaveAttack) { ShockwaveAttack(); } // 共通するCharacterControllerの移動処理 velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } |
トロールの状態に応じてそれぞれの処理を行うメソッドを呼び出しています。
ここのメソッドは後で作成します。
最後に重力を加えて、CharacterControllerのMoveメソッドで移動処理を行っています。
SetRandomDestinationメソッド
SetRandomDestinationメソッドは目的地をランダムに設定するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 目的地を設定する void SetRandomDestination() { // 最初の位置から有効範囲内のランダム位置を取得 var randomPos = defaultPos + Random.insideUnitSphere * movementRange; var ray = new Ray(randomPos + Vector3.up * 10f, Vector3.down); RaycastHit hit; // 目的地が地面になるように再設定 if (Physics.Raycast(ray, out hit, 100f, LayerMask.GetMask("Field"))) { destination = hit.point; } } |
defaultPosの位置にランダムに計算した球内の位置を足して目的地にしています。
その計算した位置から下向きにレイを飛ばしFieldレイヤーが設定されたゲームオブジェクトに衝突したら衝突した位置を目的地にしています。
主人公キャラクターやトロールが移動する地面のゲームオブジェクトのインスペクタのLayerにはFieldレイヤーを作成し取り付けておく必要があります。
今回の処理ではFieldレイヤーが設定されたゲームオブジェクトにレイが接触しなかった時の処理はしていないので、範囲外に出る位置を目的地にしないようにするか、Fieldレイヤーと接触しなかった時は別の目的地を設定するような処理が必要になります。
SetStateとGetStateメソッド
SetStateメソッドは状態を変更するメソッド、GetStateメソッドはトロールの状態を返すメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // 状態変更メソッド public void SetState(TrollState tmpState, Transform playerTransform = null) { trollState = tmpState; if (trollState == TrollState.idle) { velocity = new Vector3(0f, velocity.y, 0f); animator.SetFloat("WalkSpeed", 0f); animator.SetBool("Chase", false); SetRandomDestination(); Debug.Log("アイドル"); } else if (trollState == TrollState.patrol) { Debug.Log("パトロール"); } else if (trollState == TrollState.attack) { attackTargetTransform = playerTransform; attackTargetPos = attackTargetTransform.position; velocity = new Vector3(0f, velocity.y, 0f); animator.SetTrigger("Attack"); animator.SetBool("Chase", false); Debug.Log("通常攻撃"); } else if(trollState == TrollState.shockwaveAttack) { attackTargetTransform = playerTransform; attackTargetPos = attackTargetTransform.position; velocity = new Vector3(0f, velocity.y, 0f); animator.SetTrigger("ShockwaveAttack"); animator.SetBool("Chase", false); Debug.Log("衝撃波攻撃"); } else if (trollState == TrollState.chase) { animator.SetBool("Chase", true); attackTargetTransform = playerTransform; Debug.Log("チェイス"); } } // 状態取得メソッド public TrollState GetState() { return trollState; } |
設定する状態がIdle状態だった時はトロールの移動速度を重力だけにし、アニメーションパラメータのWalkSpeedを0にします。
またChaseをfalseにし、追いかける状態を解除します。
さらにSetRandomDestinationメソッドを呼び新しい目的地を設定します。
Patrol状態だった時は特に何もしません。
Attack状態だった時は受け取った攻撃対象のTransform情報をattackTargetTransformに入れておき、そこから現在の相手の位置をattackTargetPosに入れておきます。
アニメーションパラメータのAttackをトリガーし、Chaseをfalseにします。
ShockwaveAttack状態だった時はAttack状態だった時とほぼ同じでアニメーションパラメータのShockwaveAttackをトリガーします。
Chase状態だった時はアニメーションパラメータのChaseをtrueにし、追いかける対象のTransform情報をattackTargetTransformに記憶します。
GetStateメソッドはトロールの状態を返しているだけです。
Idleメソッド
IdleメソッドはIdle状態の時にマイフレーム実行する処理です。
1 2 3 4 5 6 7 8 9 10 11 | // Idle状態の時の処理 private void Idle() { elapsedTimeOfIdleState += Time.deltaTime; // 一定時間が経過したらpatrol状態にする if (elapsedTimeOfIdleState >= timeToStayInIdle) { elapsedTimeOfIdleState = 0f; SetState(TrollState.patrol); } } |
elapsedTimeOfIdleStateにIdle状態の経過時間を記憶しておきます。
timeToStayInIdle時間を越えたらPatrol状態に変更します。
Patrolメソッド
PatrolメソッドはPatrol状態の時にマイフレーム実行する処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Patrol状態の時の処理 private void Patrol() { // 通常移動処理 if (characterController.isGrounded) { velocity = Vector3.zero; // 目的地の方向を計算し、向きを変えて前方に進める var direction = (destination - transform.position).normalized; animator.SetFloat("WalkSpeed", direction.magnitude); var targetRot = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(destination - transform.position), Time.deltaTime * rotateSpeed); transform.rotation = Quaternion.Euler(transform.eulerAngles.x, targetRot.eulerAngles.y, transform.eulerAngles.z); velocity = transform.forward * walkSpeed; } // 目的地に着いたらidle状態にする if (Vector3.Distance(transform.position, destination) < 0.5f) { SetState(TrollState.idle); } } |
トロールが接地している時は目的地の方向を計算し、トロールが徐々にその方向を向くように計算しています。
トロールが向いている前方に歩くスピードをかけて速度に足しています。
目的地と十分近づいたらIdle状態にします。
Chaseメソッド
ChaseメソッドはChase状態の時にマイフレーム実行する処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Chase状態の時の処理 private void Chase() { // 目的地を毎回設定し直す destination = attackTargetTransform.position; // 追いかける処理 if (characterController.isGrounded) { velocity = Vector3.zero; // 追いかける時はキャラクターの向きに回転して進ませる var direction = (destination - transform.position).normalized; var targetRot = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(destination - transform.position), Time.deltaTime * rotateSpeed); transform.rotation = Quaternion.Euler(transform.eulerAngles.x, targetRot.eulerAngles.y, transform.eulerAngles.z); velocity = transform.forward * chaseSpeed; } } |
Chaseメソッドが呼ばれる度に追いかける対象を目的地に設定し直します。
トロールが接地している時は追いかける対象の方向を計算し、そちらの方向を徐々に向くようにしています。
トロールの前方に追いかけるスピードをかけて移動速度に設定しています。
AttackとShockwaveAttackメソッド
AttackメソッドとShockwaveAttackメソッドはほぼ同じなので一緒に解説します。
本当はShockwaveAttackの時だけトロールが持っているメイスを攻撃対象のいた位置に合わせるようにしようと思いましたが、うまくいかなかった為やめました。(^_^;)
そんなわけで同じ処理になりました・・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // Attack状態の時の処理 private void Attack() { // 攻撃状態になった時のキャラクターの向きを計算し、徐々にそちらの向きに回転させる var targetRot = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(attackTargetPos - transform.position), Time.deltaTime * 2f); transform.rotation = Quaternion.Euler(transform.eulerAngles.x, targetRot.eulerAngles.y, transform.eulerAngles.z); // Attackアニメーションが終了したらIdle状態にする if (animator.GetCurrentAnimatorStateInfo(0).IsName("Attack") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f) { SetState(TrollState.idle); } } // ShockwaveAttackの時の処理 private void ShockwaveAttack() { // 攻撃状態になった時のキャラクターの向きを計算し、徐々にそちらの向きに回転させる var targetRot = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(attackTargetPos - transform.position), Time.deltaTime * 2f); transform.rotation = Quaternion.Euler(transform.eulerAngles.x, targetRot.eulerAngles.y, transform.eulerAngles.z); // ShockwaveAttackアニメーションが終了したらIdle状態にする if (animator.GetCurrentAnimatorStateInfo(0).IsName("ShockwaveAttack") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f) { SetState(TrollState.idle); } } |
攻撃の時も追いかける時と同じように対象の方向を徐々に向かせます。
それぞれの攻撃アニメーションが終了したらIdle状態にします。
これでトロールの行動処理スクリプトが出来上がりました。
主人公を検知する範囲の作成
実際にトロールの行動を変化させる為にはTrollScript2のSetStateメソッドを呼び出す必要があります。
まずはChase状態にする為の範囲をトロールに作成します。
GiantTrollの子要素に空のゲームオブジェクトを作成し、名前をChaseAreaとします。
ChaseAreaにはAdd ComponentからPhysics→Sphere Colliderを選択し取り付けます。
物理的に当たらないようにする為、Is Triggerにチェックを入れ、コライダのサイズを調整します。
実際のトロールのChaseAreaは、
上のような感じになりました。
トロールをChase状態にするスクリプト
主人公を追いかける範囲ChaseAreaが出来たので、その範囲に主人公キャラクターがいる時にトロールを追いかける状態にするスクリプトを作成します。
スクリプトはChaseCharaという名前にし、ChaseAreaに取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ChaseChara : MonoBehaviour { private TrollScript2 trollScript2; // Use this for initialization void Start() { trollScript2 = GetComponentInParent<TrollScript2>(); } private void OnTriggerStay(Collider other) { // キャラクターが範囲内に来たら追いかける if (other.tag == "Player" && trollScript2.GetState() != TrollScript2.TrollState.chase && trollScript2.GetState() != TrollScript2.TrollState.attack && trollScript2.GetState() != TrollScript2.TrollState.shockwaveAttack ) { trollScript2.SetState(TrollScript2.TrollState.chase, other.transform); } } private void OnTriggerExit(Collider other) { // キャラクターが範囲外に出たらidle状態にする if(other.tag == "Player" && trollScript2.GetState() == TrollScript2.TrollState.chase ) { trollScript2.SetState(TrollScript2.TrollState.idle); } } } |
Startメソッドでトロールの行動処理スクリプトTrollScript2を取得します。
OnTriggerStayメソッドはコライダが他のコライダと衝突した時に呼ばれます。
その相手のコライダがPlayerタグを持つゲームオブジェクトで、トロールの状態がChase、Attack、ShockwaveAttackではない時にTrollScript2のSetStateメソッドを呼び出してChase状態に変更します。
その時にother.transformで衝突した相手である主人公キャラクターのTransformを渡しています。
OnTriggerExitメソッドはコライダが他のコライダから出ていった時に呼び出されます。
その時相手のコライダがPlayerであればトロールの状態をIdle状態にしています。
主人公を攻撃する範囲の作成
主人公を追いかける範囲を作成し、状態を変更するスクリプトが出来ました。
次はさらに主人公に近づいた時に主人公を攻撃する範囲を作成します。
GiantTrollの子要素に空のゲームオブジェクトを作成し、名前をAttackAreaとします。
AttackAreaにはAdd ComponentからPhysics→Sphere Colliderを取り付け、Is Triggerにチェックを入れコライダのサイズを調整します。
上のようにサイズを調整しました。
実際の攻撃範囲は、
上の小さい球の範囲になりました。
大きい球はChaseAreaのコライダの範囲です。
トロールをAttackかShockwaveAttack状態にするスクリプト
AttackAreaには新しいスクリプトAttackCharaスクリプトを作成し取り付けます。
AttackCharaスクリプトはAttackAreaに主人公が侵入した時にトロールを攻撃状態にする為の処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class AttackChara : MonoBehaviour { private TrollScript2 trollScript2; private Animator trollAnimator; // Use this for initialization void Start () { trollScript2 = GetComponentInParent<TrollScript2>(); trollAnimator = trollScript2.GetComponent<Animator>(); } private void OnTriggerStay(Collider other) { // 攻撃状態でない時に攻撃(アニメーションが攻撃状態でない時も条件に含める) if(other.tag == "Player" && trollScript2.GetState() != TrollScript2.TrollState.attack && !trollAnimator.GetCurrentAnimatorStateInfo(0).IsName("Attack") && trollScript2.GetState() != TrollScript2.TrollState.shockwaveAttack && !trollAnimator.GetCurrentAnimatorStateInfo(0).IsName("ShockwaveAttack")) { // ランダムに攻撃を振り分ける if (Random.value <= 0.5f) { trollScript2.SetState(TrollScript2.TrollState.attack, other.transform); } else { trollScript2.SetState(TrollScript2.TrollState.shockwaveAttack, other.transform); } } } } |
主人公キャラを検知した時に、トロールがAttack状態でなく、AnimatorControllerのAttack状態にいない時、ShockwaveAttack状態でない時で、AnimatorControllerのShockwaveAttack状態にいない時にトロールをAttackかShockwaveAttack状態にします。
条件がいっぱいありますが、GetStateメソッドでトロールの状態だけだと抜けがあるので、トロールのアニメーションの状態も条件に加えました。
Attack状態になるかShockwaveAttack状態になるかはRandom.valueで0~1の間のランダム値を求め、0.5以下の値であればAttack、それ以外の時はShockwaveAttack状態に変更する事にしました。
ここで攻撃の振り分けをしていますが、TrollScript2スクリプトで振り分けをした方が理にかなっているかもしれません。(^_^;)
トロールの武器の設定
トロールのプレハブは既にメイスを持っています。
そこでメイスにコライダとスクリプトを設定し、主人公キャラを攻撃出来るようにしていきます。
基本的には、
の記事と同じように武器の設定をしていきますが、今回はトロールの持つメイスと主人公キャラクターが衝突する設定にしてみたいと思います。
メイスと主人公が衝突すると主人公がメイスに乗ってしまうという問題が出ますが、武器と主人公が物理的に衝突する処理もやっておいて損はないかなと思ってそちらにしました。
物理的な衝突を排除したい場合はメイスのコライダのIs Triggerにチェックを入れ、それ用に対応してみてください。
ではGiantTrollの子要素のmace_lowの子要素に空のゲームオブジェクトを2つ作成し、名前をMaceColliderとCreateShockwavePointとします。
MaceColliderのインスペクタのAdd ComponentからPhysics→Capsule ColliderとPhysics→Sphere Colliderを取り付け、メイスの見た目に応じてサイズを変えます。
物理的に衝突させる為、コライダのIs Triggerにチェックは入れません。
さらにAdd ComponentからPhysics→Rigidbodyを取り付け、Collision DetectionをContinuous Dynamicにし当たり判定を細かくチェック出来るようにし、Constraintsに全てチェックを入れ動きを制限します。
メイスが当たった他のRigidbody&コライダを持つゲームオブジェクトに力を加えるならばMass(質量)の数値も大きくしておくといいと思います。
メイスのコライダは、
上のような感じで作成しました。
次にCreateShockwavePointですが、これはトロールが衝撃波を伴う攻撃をした時にメイスの先から衝撃波のゲームオブジェクトを登場させる位置に利用します。
メイスの地面と接触する場所に作成しました。
衝撃波を登場させるタイミングは後でアニメーションイベントを使って作成します。
メイスが主人公に当たった時の処理をするスクリプト
メイスが主人公に当たった時に主人公にダメージを与える処理が必要になります。
そこでMaceColliderに新しいスクリプトMaceを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Mace : MonoBehaviour { // 攻撃を有効にするかどうか private bool enableAttack; // メイスのコライダ群 private Collider[] maceColliders; // 攻撃相手のCharacterController [SerializeField] private CharacterController characterController; // Use this for initialization void Start () { // メイスに使っているコライダを全取得 maceColliders = GetComponents<Collider>(); } private void OnCollisionEnter(Collision collision) { // 攻撃が有効な範囲のアニメーションでなければ何もしない if(!enableAttack) { return; } if(collision.gameObject.tag == "Player") { var particleShockwaveChara = collision.gameObject.GetComponent<ParticleShockwaveChara>(); // キャラがダメージ状態でなければダメージを与える if (particleShockwaveChara.GetState() != ParticleShockwaveChara.State.damage) { particleShockwaveChara.Damage(); // キャラが攻撃を受けた時にメイスとの衝突を無効にする IgnoreCollision(true); } } } // 攻撃の有効・無効の切り替えメソッド public void ChangeEnableAttack(bool flag) { // 攻撃開始時にはキャラとメイスの衝突を有効にしておく if(flag) { IgnoreCollision(false); } enableAttack = flag; } // ダメージ時のキャラとメイスの衝突を切り替えるメソッド public void IgnoreCollision(bool flag) { foreach (var item in maceColliders) { Physics.IgnoreCollision(item, characterController, flag); } } } |
enableAttackはトロールの攻撃アニメーションで実際に主人公に攻撃を与えることが出来るかどうかのフラグです。
アニメーションイベントを使ってアニメーションの位置に応じてこのフラグをオン・オフしダメージを与えることが出来るかどうかを判断します。
maceCollidersはメイスのコライダを入れておくフィールドです。
StartメソッドでGetComponentsを使って自身のコライダを全て取得します。
OnCollisionEnterメソッドはコライダが衝突した時に呼ばれます。
enableAttackがfalseの時は攻撃が有効でないのでその後の処理は実行しません。
衝突した相手が主人公キャラクターだった時は主人公キャラの操作スクリプトを取得し、キャラクターの状態がダメージ状態でなければキャラクターのDamageメソッドを呼び出します。
キャラクターにダメージを与えた時にメイスのコライダとキャラクターのコライダの衝突を無視するようにする為、IgnoreCollisionメソッドを呼び出します。
ChangeEnableAttackメソッドでは攻撃の有効・無効を切り替えるメソッドでflagがtrueの時は攻撃が有効になる時なので、IgnoreCollisionメソッドにfalseを渡して主人公キャラクターのコライダとメイスのコライダの衝突を有効にします。
IgnoreCollisionメソッドはメイスとキャラクターのコライダの衝突を切り替えるメソッドです。
メイスに設定したコライダ群と主人公キャラクターのコライダの衝突の無視の設定をします。
第3引数が無視するかどうかのbool値を渡しますが、trueであれば衝突が無効、falseであれば衝突が有効となります。
Maceスクリプトのインスペクタで主人公キャラクターのCharacterControllerのコライダを設定しておきます。
ここからわかる通り、メイスと攻撃対象のコライダの衝突の有効・無効の切り替え機能は主人公キャラクターだけとなります。
複数の主人公と他のキャラクターがいる場合は衝突の有効・無効を攻撃した相手のコライダを取得し、有効・無効を切り替える必要があります。
面倒な場合はダメージ状態でもメイスとキャラクターの衝突は無視せずそのままにしてしまうというのがいいかもしれません。
アニメーションイベントの設定
トロールの武器の設定が終わりましたが、このままだとトロールが歩いてきてメイスが主人公と接触しただけでダメージを受けてしまいます。
そこで、アニメーションの再生場所に応じてMaceスクリプトのenableAttackのフラグをオン・オフして切り替えてダメージを与えることが出来るアニメーションの範囲を設定します。
トロールのアニメーターコントローラーのAttackにはcave_trollのアニメーションのattack1、ShockwaveAttackにはattack2を設定します。
そこでattack1、attack2にアニメーションイベントを作成していきます。
詳しいアニメーションイベントの作り方は、
を参照してください。
今回はattack1のアニメーションにStartAttack、EndAttackを作成し、attack2のアニメーションにStartAttack、EndAttack、CreateShockwaveを作成します。
attack2は以下のようになりました。
StartAttackはメイスを振り下ろす時、EndAttackはメイスを下まで振り下ろした時、CreateShockwaveはEndAttackと同じかそれと近い位置に作成します。
attack1にもStartAttackとEndAttackを作成してください。
これでアニメーションイベントが発生しスクリプトでこれらのイベントを受け取ることが出来るようになりました。
アニメーションイベントの受け取りスクリプト
アニメーションイベントを作成したのでそれを受け取るスクリプトをGiantTrollゲームオブジェクトに取り付けます(アニメーションイベントはAnimatorを持つオブジェクトと同じ階層に送られる)。
ReceiveAttackEventスクリプトを作成し、GiantTrollに取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ReceiveAttackEvent : MonoBehaviour { private Mace mace; [SerializeField] private GameObject shockwavePrefab; [SerializeField] private Transform createShockwavePoint; // Use this for initialization void Start () { mace = GetComponentInChildren<Mace>(); } // 攻撃開始時 public void StartAttack() { mace.ChangeEnableAttack(true); } // 攻撃終了時 public void EndAttack() { mace.ChangeEnableAttack(false); } // 衝撃波発生 public void CreateShockwave() { Instantiate(shockwavePrefab, createShockwavePoint.position, shockwavePrefab.transform.rotation); } } |
shockwavePrefabには衝撃波のプレハブをインスペクタで設定します。
createShockwavePointにはメイスの子要素に作成したCreateShockwavePointゲームオブジェクトをドラッグ&ドロップで設定します。
StartメソッドでmaceにMaceスクリプトを取得します。
StartAttackメソッドがアニメーションイベントで呼ばれたらMaceスクリプトのChangeEnableAttackメソッドを呼び出し、攻撃を有効にします。
EndAttackメソッドが呼ばれたら、攻撃を無効にします。
CreateShockwaveメソッドが呼ばれたら衝撃波のプレハブをインスタンス化し登場させます。
衝撃波のインスタンスは衝撃波のプレハブの角度を設定して、衝撃波の角度が同じ角度でインスタンス化されるようにしています。
トロールの攻撃アニメーションの足を動かさないようにする
トロール用のattack1とattack2のアニメーションは攻撃時に足元が動いています。
そこでアニメーションのRoot Transform Position(XZ)のBake Into Poseにチェックを入れて足元が動かないようにします。
これで全ての機能が出来上がりました!(^^)/
終わりに
今回はトロールの持つ武器と主人公キャラクターが衝突するようにした為、キャラクターが相手の武器にも乗れるようになっています。
その為、本来キャラクターが到達出来ない高さまで移動出来てしまい不具合が発生する可能性があります。
今回はダメージを受けたら次の攻撃までメイスと主人公のコライダの衝突を無視してある程度衝突を避けるようにしていますが、ダメージを受けた時に主人公を吹き飛ばすような処理を入れるのもいいかもしれませんね。
後は主人公キャラクターのCharacterControllerのコライダは移動にのみ使って、当たり判定用のコライダは別途用意するというのもいいかもしれません。
またトロールに設定した衝突のコライダが最小限なので、キャラクターのジャンプ力が高ければ両足の上にキャラクターが乗るということもあります。
ここら辺は細かくボーンの子要素にコライダを設定して対処出来ます。
巨人が出てくるゲームをやってどのような仕様になっているか確かめてみよう・・・・( ..)φメモメモ