今回は巨人がジャンプして地面に着地した時等に発生するサークル状の衝撃波の作成をしてみたいと思います。
今回作成する衝撃波はProBuilderで作ったオブジェクトを衝撃波に見立て、主人公キャラクターに当たるとダメージを与えるようにします。
パーティクルシステムで同じような機能を作成出来ますが、そちらは
を参照してください。
今回の機能を作成すると、
上のような感じのものが出来上がります。
衝撃波の作成
ProBuilderで衝撃波のモデルを作成する
まずは衝撃波のモデルをProBuilderで作成していきます。
ProBuilderに関しては、
を参照してください。
ProBuilderのNew Shape ToolでTorusを選択し、ドーナツのモデルを作成します。
設定は、
上のような感じにして、サークル状のモデルを作成します。
ヒエラルキー上で名前をCircleShockwave0とします(名前は適当に付けてください)。
出来た衝撃波のモデルは、
上のようになりました。
次に作成したオブジェクトの基点が端っこになっていますので、ProBuilderのCenter Pivotを押し、サークルの真ん中を基点とします。
Center Pivotアイコンをクリックすると、基点がサークルの中心になります。
これで衝撃波のゲームオブジェクトが出来たので、ExportでUnityのプレハブに変換します。
ExportアイコンをクリックしてAssetを選択し、Unityのプレハブにします。
Exportの設定画面を開くにはAltキーを押しながらExportアイコンをクリックし、Assetを選択し、保存先を指定します。
プレハブが出来たらヒエラルキー上のCircleShockwave0を削除して、プレハブにしたCircleShockwave0をヒエラルキー上にドラッグ&ドロップします。
次に衝撃波用のマテリアルの設定を行います。
Assetsエリア内で右クリック→Create→Materialを選択し、名前をCircleShockwave0とします。
上のような感じで設定を行いました(マテリアルは自由に設定してください)。
作成出来たらCircleShockwave0ゲームオブジェクトにドラッグ&ドロップして設定をします。
Rigidbodyの取り付けとコライダの取り付け
衝撃波はサークル状のゲームオブジェクトの大きさを段々と大きくして表現します。
その為、キャラクターとの接触判定を行う必要がある為、CircleShockwave0のインスペクタからPhysics→Rigidbodyコンポーネントの取り付けと、Physics→Mesh Colliderコンポーネントの取り付けを行います。
RigidbodyのIs Kinematicにチェックを入れスクリプトからの操作にし、Collision DetectionにはContinuous Dynamicを選択して速い動きにも対応出来るようにします。
詳しい設定は次のスクリプトの作成後に画像を掲載します。
サークル状のゲームオブジェクトに設定するスクリプトの作成
サークル状のゲームオブジェクトが出来たので、このゲームオブジェクトを時間と共に大きくして衝撃波を表現するようにします。
その為、CircleShockwave0に新しくCircleShockwaveScriptスクリプトを作成し取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class CircleShockwaveScript : MonoBehaviour { // ScaleUp用の経過時間 private float elapsedScaleUpTime = 0f; // Scaleを大きくする間隔時間 [SerializeField] private float scaleUpTime = 0.03f; // ScaleUpする割合 [SerializeField] private float scaleUpParam = 0.1f; // パーティクル削除用の経過時間 private float elapsedDeleteTime = 0f; // パーティクルを削除するまでの時間 [SerializeField] private float deleteTime = 5f; // Update is called once per frame void Update () { elapsedScaleUpTime += Time.deltaTime; elapsedDeleteTime += Time.deltaTime; // 一定時間が経ったら衝撃波を削除する if(elapsedDeleteTime >= deleteTime) { Destroy(gameObject); } // サークルを段々大きくする if (elapsedScaleUpTime > scaleUpTime) { transform.localScale += Vector3.one * scaleUpParam; // transform.localScale += new Vector3(scaleUpParam, 0f, scaleUpParam); elapsedScaleUpTime = 0f; } } } |
このスクリプトでは一定時間が経過したらゲームオブジェクトのScaleを大きくし衝撃波に見立てる処理と、一定時間が経過したら衝撃波を削除する処理を記述しています。
サークルを大きくする処理内でコメントにしてある処理がありますが、これはScaleをX、Y、Z軸ですべて大きくしていくと見た目が相対的に大きくなっていいんですが、衝撃波のコライダがキャラクターとうまく当たらない場合があります。
Y軸のScaleを大きくしないようにすると当たるようになりました。
それがコメントにしてある処理です。
これで衝撃波が完成しました。
CircleShockwave0のインスペクタでTagに新しくShockwaveを作り、設定しておきます。
衝撃波のインスペクタは
上のようになりました。
ここまでで出来ている衝撃波を確認すると、
上のようになります。
衝撃波と衝突するキャラクターの作成
衝撃波が出来たので、次は衝撃波と衝突してダウンするキャラクターを作成していきます。
キャラクターにはスタンダードアセットのEthanを使い、CharacterControllerの取り付けと設定、AnimatorControllerの作成とAnimatorへの設定を行っておきます。
AnimatorControllerの作成
AnimatorControllerは
上のような感じで作成します。
アニメーションパラメータはFloat型のSpeed、Trigger型のDamageを作成します。
Idle→WalkはSpeedがGreaterで0.1でHas Exit Timeのチェックを外し、
Walk→IdleはSpeedがLessで0.1でHas Exit Timeのチェックを外し、
AnyState→DamageはDamageがトリガーされた時でHas Exit Timeのチェックを外し、遷移の矢印を選択し、インスペクタのSettingsのCan Transition To Selfのチェックを外してDamage→Damageという遷移をしないようにしておきます。
Damage→IdleはHas Exit Timeにチェックをして、それ以外の条件はなしにしておきます。
今回キャラクターはジャンプ出来るようにしますが、ジャンプ中にジャンプアニメーションは再生しないようにしています。
キャラクター操作スクリプトの作成
キャラクター操作スクリプトParticleShockwaveCharaを作成しキャラクターに設定します。
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ParticleShockwaveChara : MonoBehaviour { public enum State { normal, damage } private CharacterController characterController; private Animator animator; private Vector3 velocity; [SerializeField] private float walkSpeed = 2f; [SerializeField] private float jumpPower = 7f; private State state; // Use this for initialization void Start () { characterController = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); velocity = Vector3.zero; state = State.normal; } // Update is called once per frame void Update () { if (state == State.normal) { if (characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); if (input.magnitude > 0.1f) { transform.LookAt(transform.position + input.normalized); animator.SetFloat("Speed", input.magnitude); velocity = transform.forward * walkSpeed; } else { animator.SetFloat("Speed", 0f); } if (Input.GetButtonDown("Jump")) { velocity.y += jumpPower; } } } else if(state == State.damage) { if(animator.GetCurrentAnimatorStateInfo(0).IsName("Damage") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f) { SetState(State.normal); } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } public void Damage() { animator.SetTrigger("Damage"); velocity = new Vector3(0f, velocity.y, 0f); SetState(State.damage); } public void SetState(State tempState) { state = tempState; } public State GetState() { return state; } } |
スクリプト内の処理に関しては他の記事でもだいぶやっているので、詳細は省きます。
簡単に言うとノーマル状態の時は地面を歩き、ジャンプボタンを押すとジャンプします。
ダメージ状態の時はダメージアニメーションが終了したらノーマル状態に戻します。
衝撃波との衝突を検知するスクリプト
次に衝撃波と衝突したかどうかを検知するスクリプトを作成していきます。
衝撃波の方からキャラクターを検知する事が出来なかったので、今回はキャラクターに取り付けたCharacterControllerで衝撃波の方を検知したいと思います。
キャラクターにCollisionShockwaveスクリプトを作成し取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class CollisionShockwave : MonoBehaviour { private ParticleShockwaveChara particleShockwaveChara; private CharacterController characterController; // Use this for initialization void Start () { particleShockwaveChara = GetComponent<ParticleShockwaveChara>(); characterController = GetComponent<CharacterController>(); } private void OnControllerColliderHit(ControllerColliderHit hit) { if (hit.collider.tag == "Shockwave" && particleShockwaveChara.GetState() != ParticleShockwaveChara.State.damage) { particleShockwaveChara.Damage(); Physics.IgnoreCollision(characterController, hit.collider, true); Debug.Log("接触"); } } } |
Startメソッドでキャラクター操作スクリプトParticleShockwaveCharaとCharacterControllerを取得しています。
CharacterControllerで接触した相手を検知するにはOnControllerColliderHitメソッドを使用する必要があります。
その中で接触した相手がShockwaveタグを設定していて、自身の状態がダメージ状態でない時にキャラクター操作スクリプトParticleShockwaveCharaのDamageメソッドを呼び出しダメージ処理を行います。
衝撃波は物理的に当たるようにしている為、一度キャラクターと衝撃波が接触したらPhysics.IgnoreCollisionメソッドを使って自身のCharacterControllerのコライダと接触した衝撃波のコライダの衝突を無視するようにしています。
Physics.IgnoreCollisionを使えばわざわざレイヤー設定を変更する必要がなくお互いのコライダの衝突無視をすることが出来るので便利です。
これでキャラクターの作成も終わりました。
キャラクターのインスペクタは
上のようになります。
衝撃波を生成するスクリプトの作成
衝撃波、キャラクターの作成が終わり、試しに衝突させようと思いますが、どうせなら衝撃波をどんどん生成し確認を容易にしたいと思います。
Main CameraにInstantiateParticleスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class InstantiateParticle : MonoBehaviour { [SerializeField] private GameObject particlePrefab; // Update is called once per frame void Update () { if (Input.GetButtonDown("Fire1")) { RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out hit, 100f, LayerMask.GetMask("Field"))) { var ins = Instantiate(particlePrefab, hit.point + Vector3.up * 0.5f, particlePrefab.transform.rotation); } } } } |
インスペクタで作成した衝撃波のプレハブを設定出来るようにしています。
地面をクリックした位置に衝撃波を生成するようにしています。
地面のゲームオブジェクトのインスペクタのLayerにあらかじめFieldレイヤーを作成し、設定しておいてください。
これで全ての機能が出来ました!
物理的に当たる衝撃波の問題点
今回作成した衝撃波はProBuilderで作った物理的にキャラクターと当たる衝撃波です。
その為、キャラクターとぶつかった時に衝撃波のコライダが物理的にキャラクターを除けるような感じになります。
また一度衝撃波と衝突したら同じ衝撃波には二度三度とぶつからないようにしたいところです。
なので接触した際に物理的な接触を解除したい場合はキャラクターのCharacterControllerを無効化するか衝撃波のコライダを無効化したりする必要があります。
そんなわけで一度キャラクターと衝撃波が衝突した場合はPhysics.IgnoreCollisionメソッドを使ってお互いのコライダの衝突を無視するようにしていますが、最初の衝突時はキャラクターのコライダが少し浮いてしまいます。
そもそも衝撃波のMeshColliderのIs Triggerを有効にして物理的に当たらないようにすればよさそうなんですが、Is Triggerを有効にする為にはConvexを有効にする必要があります。
Convexを有効にするとサークル状の中身(ドーナツの穴)も有効になってしまうのでドーナツ部分、ドーナツの穴の部分のコライダが接触範囲となってしまいます。
そんなわけで思うような衝撃波を作るには少々面倒くさい事になっていきます。
また今回の記事では元のサークル状のゲームオブジェクトのScaleを少しづつ大きくして衝撃波を表現している為、キャラクターに当たっているように見えて当たらない事があります。
そういう理由もあって次回はパーティクルシステムを使ってサークル状の衝撃波を作成していきます。