今回からUnityを使ってバイオハザード風ガンシューティングの機能を作っていきたいと思います。
あくまで風なので、バイオハザード2と同じバイオハザード4と同じ、というわけにはいきません。
キャラクターが銃を装備していたら、何か特定のキーを押している間は銃を構え、どこを狙っているかわかりやすい表示(レーザーポインタ)をして、敵にレーザーポインタが当たっていて銃を撃つキーを押せば敵にダメージを与えます。
また、次回以降に銃をかまえている間は上半身を少し動かし狙う角度の調整も出来るようにします。
なんだか盛りだくさんで嫌になりそうですが・・・(^_^;)
今回は銃を装備させ、銃を構えるようにしてみます。
今回の機能は以前作成した機能に処理を追加する形で作成されていますので、以前作成した機能について知りたい方は
の記事で今までの機能作成の流れを確認する事が出来ます。
銃の3Dオブジェクトの準備とキャラクターに装備させる
銃はAsset Storeで準備しましょう。
3D モデル→小道具→武器→銃火器→[PBR] Makarov- Free Edition
をインポートさせていただきました。
キャラクターの右手の子要素にCreate Emptyで空オブジェクトを作り、名前をEquipとしています。
その子要素に銃を持たせます。
上のような感じに置きます。
上の階層ではEquipの子要素に装備する武器を並べていますが、武器をインスタンス化して配置する場合はMakrovだけになります。
Scaleを調整し、位置と角度を変更します。
Scaleは本来は銃のモデルのインスペクタのScale Factorの値を変更してサイズ調整をしたいところですが、モデル内部の部品でScale調整がされており、Scale Factorを使うと銃がバラバラになってしまう為、今回はScaleを調整しました。
Scale Factorを調整しても銃の部品に影響がなければScale Factorを使用して銃のサイズを調整してください。
銃の親要素であるEquipのTransformのScaleのX、Y、Zが1である事を必ず確認してください。
ここがそれ以外の値であると子要素である銃のTransformはそこからの相対値になるので、位置や角度を変更するとオブジェクトがゆがみます。
また銃をもっと正しく手の位置、角度と合わせたい時は
も参考にしてください。
武器のステータス記憶用のスクリプトWeaponStatusを追加し、インスペクタでWeaponTypeをGunに変更します。
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 | using UnityEngine; using System.Collections; public class WeaponStatus : MonoBehaviour { public enum WeaponType { Sword, Gun, Other } [SerializeField] private int attackPower; [SerializeField] private int shotPower; [SerializeField] private WeaponType weaponType; [SerializeField] private float weaponRange; public int GetAttackPower() { return attackPower; } public int GetShotPower() { return shotPower; } public WeaponType GetWeaponType() { return weaponType; } public float GetWeaponRange() { return weaponRange; } } |
銃で殴るという場合も考えてCapsuleColliderを追加しチェックを外しておきます。
Is Triggerのチェックを入れておきます。
主人公の打撃攻撃の作成は
を参考にしてください。
回転をさせる場合は上の部分をクリックして操作すると視覚的にわかりやすいです。
武器の配置が出来たら名前をHandGunに変更しAssetsフォルダにドラッグ&ドロップしてプレハブにし、ヒエラルキー上のHandGunは削除します。
インスペクタでweaponsにプレハブのHandGunをドラッグ&ドロップします。
これで武器の切り替えをした時にHandGunが加わりました。
銃を構えるアニメーションの準備と設定
次に銃を撃つ時のアニメーションを用意してください。
AssetStoreでは見つけられませんでした(無料で)。ちゃんと探せばあると思いますが・・・。
アニメーションの作り方は
を参考にして作成してみてください。
それではアニメーターコントローラーに構える状態を作ります。
まずBool型のWaitShotというアニメーションパラメータを作成します。
上のようにWaitShotというステートを作り銃を構えるアニメーションクリップを設定し、Idle、Walk、Damageから遷移を繋げ、WaitShotがtrueになったらWaitShotステートに遷移するように条件を加えます。
WaitShotステートからはIdleに遷移を繋げ、条件にWaitShotがfalseになった時に設定します。
それぞれの条件ではHas Exit Timeのチェックを外しておきます。
これでアニメーションの流れが出来たので、スクリプトでアニメーション操作の処理を足していきます。
銃を構えるアニメーションに遷移させるスクリプトを作成する
構えるアニメーションにする為に押すボタンは「Fire2」に設定されているボタンにします。
Windowsパソコンの場合はデフォルトで右クリックになっています。
右クリックを離した時点で構えをやめるようにします。
これらのボタンの動作の処理はキャラクター操作スクリプトでやっているのでそちらに処理を加えていきます。
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 | public enum MyState { Normal, Damage, Attack, WaitShot }; // Shotスクリプト private Shot shot; private MyStatus myStatus; void Start () { shot = GetComponent <Shot> (); myStatus = GetComponent<MyStatus>(); } void Update() { // 接地していたら速度を初期化 if(characterController.isGrounded) { velocity = Vector3.zero; } if (state == MyState.Normal) { // 地面に接地してる時は速度を初期化 if (characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); if (input.magnitude > 0f) { animator.SetFloat("Speed", input.magnitude); transform.LookAt(transform.position + input); velocity += input.normalized * walkSpeed; } else { animator.SetFloat("Speed", 0f); } if (Input.GetButtonDown("Fire1")) { SetState(MyState.Attack); } else if (Input.GetButton("Fire2") && myStatus.GetWeaponStatus() != null && myStatus.GetWeaponStatus().GetWeaponType() == WeaponStatus.WeaponType.Gun ) { SetState(MyState.WaitShot); animator.SetBool("WaitShot", true); } } } else if(state == MyState.WaitShot) { // マウスの右ボタンを離したらノーマル状態へ遷移 if (!Input.GetButton("Fire2")) { animator.SetBool("WaitShot", false); shot.DisableLight(); shot.DisableRaserPointer(); SetState(MyState.Normal); // 構えた状態 } else { shot.AbleRaserPointer(); // 銃を撃つ if (Input.GetButtonDown("Fire1")) { shot.JudgeShot(); } } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } |
MyState.Normal状態の時は移動を行い、その中でFire1ボタンを押したら通常の打撃攻撃、銃を装備している時でFire2ボタンを押したら銃を構えるようにします。
MyState.WaitShot状態の時は銃を構えた状態なのでその中で、マウスの右ボタンを離した時はノーマル状態へと遷移させます。
マウスの右ボタンを離していない時でFire1ボタンを押したら銃を撃った時にする為、銃を撃つ処理をします。
shotに保存しているShotスクリプトのメソッド呼び出しを多く記述していますが、これらは後で作成します。
Shotスクリプトのメソッドは、
AbleLightは銃を発砲した時の光を点け、
DisableLightは銃を発砲した時の光を消し、
AbleRaserPointerは銃のレーザーポインターを表示し、
DisableRaserPointerは銃のレーザーポインターを消し、
PlaySEは銃の発砲音を鳴らし、
JudgeShotは弾が敵に当たったかどうかを判断するメソッドです。
さきほど紹介したスクリプトは以前の記事で作成したスクリプトを元にしているので、この記事単体ではわからない部分もあると思いますが、
キャラクター移動の条件だったり、キャラクターが銃を構える時の条件を記述しているだけです。
キャラクター操作スクリプトのTakeDamageメソッドとSetStateメソッドを変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public void TakeDamage(Transform enemyTransform, Vector3 attackedPlace) { SetState(MyState.Damage); // characterController.Move (enemyTransform.forward * 0.5f); var damageEffectIns = Instantiate<GameObject>(damageEffect, attackedPlace, Quaternion.identity); Destroy(damageEffectIns, 1f); } public void SetState(MyState tempState) { state = tempState; if (state == MyState.Normal) { } else if (state == MyState.Attack) { velocity = Vector3.zero; animator.SetTrigger("Attack"); } else if(state == MyState.Damage) { velocity = Vector3.zero; animator.SetBool("WaitShot", false); animator.SetTrigger("Damage"); } else if(state == MyState.WaitShot) { } } |
敵にダメージを受けた時はアニメーションパラメータのWaitShotをfalseにしています。
ここまででShotスクリプトを作成していないので主人公操作スクリプトはエラーが出る状態です。
銃のレーザーポインタの作成と銃を発砲した時の光
とりあえずShotスクリプトは後回しにして、次はレーザーポインタと銃を撃った時の光の準備をします。
銃の子要素にMuzzle(銃口)をCubeで作りサイズを調整して合わせます。Z方向青矢印が銃弾の飛んでいく先とします。
見た目はいらないのでMeshRendererのチェックを外し、BoxColliderもチェックを外しておきます。
空オブジェクトにしなかったのは位置を合わせる時にやりやすいからです。
MuzzleのZ方向の青矢印を銃弾の飛ばす位置として合わす時に、上の画像のように赤い四角の部分をクリックし、Local表示にして合わせてください。
Local表示にするとそのゲームオブジェクトの向きでX、Y、Zの軸の矢印が表示されるようになります。
MuzzleにAdd ComponetでLineRendererとLight、AudioSourceを追加します。
AudioSourceには発砲時に鳴らす銃声を設定し、PlayOnAwakeのチェックは外してください。
AudioSourceについては
を参照してください。
LineRendererは設定した点に線を引く為のコンポーネントです。
Muzzleの部分から銃口が向いている先の部分まで軌跡を表示することでレーザーポインタの表現が出来ます。
Cast ShadowsをOffにしてレーザーポインターの影は落とさないようにします。
LineRendererはUnityのバージョンが違うと上の画像とは多少違います。
Widthがグラフで設定する仕様になっている場合はWidthを0.010~0.015にして横軸の数値が増えても値が変わらないように設定しておきます。
Lightは発砲した時の光を表現します。
設定は上のような感じにしました。
Lightは光の発生点から広がるPointにしました。
LineRendererに設定しているGunsRayというマテリアルを作成します。
ProjectタブのAssetsフォルダで右クリックしCreateからMaterialを選びます。
GunsRayのインスペクタでシェーダ―をParticle/Standard Unlitにし、Rendering ModeをAdditive、Color ModeをMultiplyに変更します。
Albedoの色を赤色にします。
LineRendererにマテリアルを設定し、コンポーネントのチェックを外したらレーザーポインタの準備が完了です。
Lightもチェックを外してください。
敵にレーザーポインタが当たった時にわかりやすくする為、シューティングポイントを表示します。
それをシーン上にSphereとして作成します。
Sphereの名前をShotPointという名前に変更し、マテリアルを設定します。
マテリアルはレーザーポインタのマテリアルを作った時と同様の方法で作成します。
出来たらShotPointにマテリアルを設定します。
ShotPointは最初は見えなくしておきたいので、MeshRendererのチェックを外しておきます。
このShotPointは常にシーン上にあり、敵にレーザーポインタが当たるとそこに移動し、MeshRendererのチェックをいれるという感じで使用します。
ShotPointはプレハブ化し敵にレーザーポインタが当たった時にインスタンス化するという方法もありますが、毎回インスタンス化するのは処理に時間がかかりそうなので表示のオン・オフで対応しました。
銃を構えた時の処理スクリプトShotの作成
それでは銃を構えた時の処理を実行するスクリプトShotを作成していきます。
Shotスクリプトは銃自体に設定する事も出来ますが、今回は主人公キャラクター自身に設定する事にします。
フィールド宣言部と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 | // レイの長さ private float rayRange = 1000f; [SerializeField] private Transform equip; // レーザーポインタ private LineRenderer raserPointer; // 音声ソース private AudioSource audioSource; // 発砲の光 private Light shotLight; // 発砲の光の存続時間 [SerializeField] private float lightLimit; // 発砲の光がでてからの経過時間 private float lightElapsedTime; // 主人公の行動処理スクリプト private CharacterScript characterScript; // 敵にレーザーポインタが当たった時のポイント [SerializeField] public MeshRenderer shotPoint; // 主人公のステータススクリプト private MyStatus myStatus; // レーザーポインタが何かに当たっているかどうか private bool hitFlag = false; // レイを飛ばした先で一番近い位置 private Vector3 nearPoint; // レイにヒットした一番近い位置と銃口との距離 private float distance = float.MaxValue; // 銃の発射口 [SerializeField] private Transform muzzle; |
いきなり盛りだくさん・・・・(+_+)
rayRangeはレイを飛ばす距離。
equipはインスペクタで武器の親のゲームオブジェクトのTransformを指定します。
raserPointer、audioSource、shotLightは武器が銃に変わった時に銃に設定しているコンポーネントを設定します。
lightLimitは発砲の光を表示している時間。
lightElapsedTimeは銃を発砲してからの経過時間。
characterScriptは主人公キャラ操作スクリプト。
shotPointは銃のシューティングポイント。
myStatusは主人公のステータススクリプト。
hitFlagはレーザーポインターが何かに当たっているかどうか。
nearPointはレーザーポインターが当たった一番近いゲームオブジェクトまでの距離。
distanceはレーザーポインターが当たった距離を入れるフィールド。
muzzleは銃の子要素のMuzzleを設定するフィールド。
になります。
1 2 3 4 5 6 | void Start () { myStatus = GetComponent <MyStatus> (); characterScript = GetComponent <CharacterScript> (); } |
Startメソッド内コンポーネントの取得と初期設定です。
コンポーネントの有効・無効と設定処理
Shotスクリプトの簡単な部分から記述していきます。
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 | public void AbleRaserPointer() { raserPointer.enabled = true; } public void DisableRaserPointer() { raserPointer.enabled = false; } public void AbleLight() { shotLight.enabled = true; lightElapsedTime = 0f; } public void DisableLight() { shotLight.enabled = false; } public void PlaySE() { audioSource.Play(); } // 銃の情報を取得 public void InitializeGun(GameObject weapon) { raserPointer = weapon.GetComponentInChildren<LineRenderer>(); shotLight = weapon.GetComponentInChildren<Light>(); audioSource = weapon.GetComponentInChildren<AudioSource>(); muzzle = weapon.transform.Find("Muzzle"); } |
これらはレーザーポインタの表示・非表示
発砲時の光の表示・非表示
音を鳴らす
という処理です。
銃を撃ったタイミングでこれらのメソッドを呼び出します。
これらは主人公操作スクリプトCharacterScriptで呼び出していたメソッドですね。
InitializeGunメソッドは武器を切り替えるスクリプトChangeEquipスクリプトから呼び出すメソッドで、引数で受け取った武器の子要素からコンポーネントを取得したり、Muzzleゲームオブジェクトを取得しています。
レーザーポインタを表示させる処理
次にレーザーポインタの表示処理を加えます。
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 | void Update() { // 主人公の状態が銃を構えた状態の時だけ if (characterScript.GetState() == CharacterScript.MyState.WaitShot) { lightElapsedTime += Time.deltaTime; raserPointer.SetPosition(0, muzzle.position); // 銃口からレイを作成 Ray ray = new Ray(muzzle.position, muzzle.forward); RaycastHit hit; hitFlag = false; distance = float.MaxValue; // Fieldレイヤーとの接触 if (Physics.Raycast(ray, out hit, rayRange, LayerMask.GetMask("Field"))) { shotPoint.enabled = false; hitFlag = true; nearPoint = hit.point; distance = Vector3.Distance(muzzle.position, hit.point); } // Enemyレイヤーとの接触 if (Physics.Raycast(ray, out hit, myStatus.GetWeaponStatus().GetWeaponRange(), LayerMask.GetMask("Enemy"))) { if (hit.collider.GetComponent<Enemy>().GetState() != Enemy.EnemyState.Dead) { shotPoint.enabled = true; hitFlag = true; // Fieldレイヤーとの接触よりEnemyレイヤーとの接触が近い場合 if (Vector3.Distance(muzzle.position, hit.point) < distance) { nearPoint = hit.point; shotPoint.transform.position = hit.point; // Fieldレイヤーの方が近い場合ShotPointを無効化 } else { shotPoint.enabled = false; } } } // 何らかに接触していたら接触した一番近い位置をレーザーポインタの到達点に指定 if (hitFlag) { raserPointer.SetPosition(1, nearPoint); // 衝突していなければShotPointを無効化し、レーザーポインタはMuzzleからrayRangeの長さ分いったところを到達点にする } else { shotPoint.enabled = false; raserPointer.SetPosition(1, ray.origin + ray.direction * rayRange); } if (lightElapsedTime >= lightLimit) { DisableLight(); } // 銃を構えた状態でなければレーザーポインタ、銃の光、ShotPointを無効化 } else { if (raserPointer != null) { raserPointer.enabled = false; shotLight.enabled = false; } shotPoint.enabled = false; } } |
銃を構えた状態の時にレーザーポインターの処理を行います。
LineRendererでは複数点を指定して線を引きますので、まずは最初の地点を指定します。
1 2 3 | line.SetPosition(0, muzzle.position); |
で最初の地点を指定します。
第1引数が0で最初の地点、1で次の地点となります。
この数を増やしていくと点を繋ぐ線が出来ます。
Rayはレーザー光線のようなもので位置と向きを指定します。
1 2 3 | Ray ray = new Ray (muzzle.position, muzzle.forward); |
Muzzleの位置からMuzzleの前方に向かってレイを作成します。
MuzzleのZの位置を発射させる向きにしたのはmuzzle.forwardを使う為です。
次はレイが指定したレイヤーを持つゲームオブジェクトと接触したかどうか調べる部分を見ていきましょう。
1 2 3 4 5 6 7 8 | if (Physics.Raycast (ray, out hit, rayRange, LayerMask.GetMask ("Field"))) { shotPoint.enabled = false; hitFlag = true; nearPoint = hit.point; distance = Vector3.Distance (muzzle.position, hit.point); } |
Physics.RaycastはレイをrayRangeの長さ飛ばし、Fieldレイヤーが設定されたゲームオブジェクトに接触したら条件がtrueになります。
さらにhitに接触したゲームオブジェクトの情報が入り、hit.pointで接触した位置を取得する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | if (Physics.Raycast (ray, out hit, myStatus.GetWeaponStatus ().GetWeaponRange (), LayerMask.GetMask ("Enemy"))) { if (hit.collider.GetComponent<Enemy> ().GetState () != Enemy.EnemyState.Dead) { shotPoint.enabled = true; hitFlag = true; // Fieldレイヤーとの接触よりEnemyレイヤーとの接触が近い場合 if (Vector3.Distance (muzzle.position, hit.point) < distance) { nearPoint = hit.point; shotPoint.transform.position = hit.point; // Fieldレイヤーの方が近い場合ShotPointを無効化 } else { shotPoint.enabled = false; } } } |
Fieldレイヤーとの接触の後にEnemyレイヤーを指定したものとの判定を行いますが、この場合のレイの距離は武器の射程距離にします。
こうすることで、武器の射程距離に満たない場合は敵にレイが届きません。
敵が死んだ状態でなければShotPoint(シューティングポイント)を表示し、hitFlagをtrueにし、敵に当たった事にします。
distanceには何も接触していない時はfloat.maxValueでfloat値の最大値が入り、Fieldレイヤーと接触していればそのゲームオブジェクトの距離が入っています。
Enemyレイヤーと接触した距離とdistanceの距離を比べ、Enemyレイヤーのゲームオブジェクトとの距離の方が短かった時はそちらにシューティングポイントを表示します。
シューティングポイントをhit.pointにするとコライダの位置にシューティングポイントが表示されますが、敵にかぶって見えづらい事もあります。
今後カメラをキャラクターの子要素に配置し、ラジコン操作で動かせるようにした場合はシューティングポイントを少し主人公キャラクターよりに表示した方が見やすいかもしれません。
シューティングポイントを主人公側に表示するには
1 2 3 | shotPoint.transform.position = hit.point - transform.forward * 0.1f; |
↑のようにhit.pointからtransform.forward(主人公キャラクターの前方)の反対側に少し補正値を加えます。
この場合カメラがキャラクターの後ろにあればいいですが、ない場合はtransform.forwardの代わりにCamera.main.transform.forward(この場合はメインカメラ)を使ってカメラの方向を使うといいかもしれません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 何らかに接触していたら接触した一番近い位置をレーザーポインタの到達点に指定 if (hitFlag) { raserPointer.SetPosition (1, nearPoint); // 衝突していなければShotPointを無効化し、レーザーポインタはMuzzleからrayRangeの長さ分いったところを到達点にする } else { shotPoint.enabled = false; raserPointer.SetPosition (1, ray.origin + ray.direction * rayRange); } if (lightElapsedTime >= lightLimit) { DisableLight (); } |
hitFlagがtrueならば何らかのゲームオブジェクトにレイが当たっていたので、レーザーポインターの到達点を設定します。
ゲームオブジェクトに当たっていなければシューティングポイントをオフにし、到達点をrayRange先の位置に設定します。
発砲した時の光が一定時間経過したらDisableLightメソッドを呼んで光を無効にしています。
弾が当たったかどうかの処理
弾が当たったかどうかを判断するJudgeShotメソッドを作成します。
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 | // 敵に弾が当たったかどうか public void JudgeShot() { // 銃の発砲の光を表示 AbleLight(); // 銃の発砲音を鳴らす PlaySE(); Ray ray = new Ray(muzzle.position, muzzle.forward); RaycastHit hitPoint; distance = rayRange; if(Physics.Raycast (ray, out hitPoint, myStatus.GetWeaponStatus ().GetWeaponRange (), LayerMask.GetMask("Field"))) { distance = Vector3.Distance(muzzle.position, hitPoint.point); } if(Physics.Raycast (ray, out hitPoint, myStatus.GetWeaponStatus ().GetWeaponRange (), LayerMask.GetMask("Enemy"))) { if(distance > Vector3.Distance(muzzle.position, hitPoint.point)) { var enemyObj = hitPoint.collider.gameObject.transform.root; if(enemyObj.GetComponent<Enemy>().GetState () != Enemy.EnemyState.Dead) { var enemy = enemyObj.GetComponent<Enemy>(); enemy.TakeDamage(myStatus.GetWeaponStatus().GetShotPower(), hitPoint.point); } } } } |
JudgeShotメソッドでもFieldレイヤーとEnemyレイヤーでEnemyレイヤーが近かったらダメージを与えるようにしてます。
TakeDamageで渡すダメージ値はMyStatusで保持している装備している武器の銃の威力にします。
敵キャラクターにレイヤーを設定する
ここでレイヤーについて見ていきます。
LayerMask.GetMask(“レイヤーの名前”)
でレイヤーの取得が出来ます。レイヤーはオブジェクトのインスペクタで設定が出来ます。
敵キャラクターのレイヤーをEnemyに設定します。
(レイヤーの設定はご自由に)
レイヤーがない場合は作成して追加してください。
レイヤーを変更する時に上のようなウインドウが出る事があります。
これは子要素のレイヤーも変更するかどうか聞かれています。
子要素のレイヤーを変更してしまうと不具合になる場合はNo, This Object Onlyをクリックしてください。
例えば敵キャラクターの子要素に主人公キャラを検知するサーチエリアを設定していた場合、
Yes, Change Childrenをクリックして子要素のレイヤーもEnemyにしてしまった場合、銃を撃って当たり判定をする時にサーチエリアの範囲も含めてしまうことになります。
なので、敵キャラクターの場合は子要素のレイヤーは変更しないようNo, This Object Onlyを選んでください。
万が一間違えて変更してしまっても子要素のゲームオブジェクトを選択しレイヤーを変更してあげれば大丈夫です。
ゲームオブジェクトを作成した時はDefaultレイヤーに設定されているので、特にレイヤーを設定する必要がないものはDefaultレイヤーに戻すといいと思います。
Enemyスクリプトの修正
敵のHPが0以下になった時にDead状態へと遷移させていますが、Dead状態になったら他の処理をしないように変更します。
まずはUpdateメソッドの最初でEnemyState.Dead状態であればreturnで処理をしないようにします。
1 2 3 4 5 6 | void Update () { if (state == EnemyState.Dead) { return; } |
次はSetStateメソッドです。
1 2 3 4 5 6 7 8 | // 敵キャラクターの状態変更メソッド public void SetState(string mode, Transform obj = null) { // 死んでたら状態変更しない if (state == EnemyState.Dead) { return; } |
こちらも死んだ状態であればreturnで処理をさせません。
武器の位置、角度、サイズをWeaponStatusスクリプトに移す
武器の切り替えはChangeEquipスクリプトで行い、そこでは直にそれぞれの武器の位置や角度、サイズを設定していましたが、これをWeaponStatusに保存し、そこからデータを取って来るようにします。
WeaponStatusに
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // インスタンス化した時の武器の位置、角度、サイズデータ [SerializeField] private Vector3 pos; [SerializeField] private Vector3 rot; [SerializeField] private Vector3 scale; public Vector3 GetPos() { return pos; } public Vector3 GetRot() { return rot; } public Vector3 GetScale() { return scale; } |
というフィールドを宣言し、それぞれのフィールドを返すメソッドを定義します。
武器のプレハブでそれぞれの値を設定します。
ChangeEquipスクリプトに追記と修正をします。
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 | private Shot shot; void Start() { shot = GetComponent<Shot>(); } void InstantiateWeapon() { equipment++; if (equipment >= weapons.Length) { equipment = -1; } // 今装備している武器を削除 if (equipTransform.childCount != 0) { Destroy(equipTransform.GetChild(0).gameObject); } // 素手ではない時だけ武器をインスタンス化 if (equipment != -1) { // 新しく装備する武器をインスタンス化 var weapon = Instantiate<GameObject>(weapons[equipment]); weapon.name = weapons[equipment].name; processCharaAnimEvent.SetCollider(weapon.GetComponent<Collider>()); var weaponStatus = weapon.GetComponent<WeaponStatus>(); // 武器の位置や角度を設定 weapon.transform.SetParent(equipTransform); weapon.transform.localPosition = weaponStatus.GetPos(); weapon.transform.localEulerAngles = weaponStatus.GetRot(); weapon.transform.localScale = weaponStatus.GetScale(); if (weapon.CompareTag("Sword")) { animator.SetBool("HaveSword", true); } else { animator.SetBool("HaveSword", false); } myStatus.SetEquip(weapon); // 武器を変更し、銃だったら情報をセット if (weaponStatus.GetWeaponType() == WeaponStatus.WeaponType.Gun) { shot.InitializeGun(weapon); } } else { animator.SetBool("HaveSword", false); } } |
武器のインスタンス化をした時にインスタンス化したゲームオブジェクトの名前をプレハブの名前にしました。
weaponStatusのゲッターでインスタンス化した時の位置、角度、サイズを取得し、それを設定します。
インスタンス化した武器が銃の時はShotスクリプトのInitializeGunメソッドで銃の初期化処理を行います。
レーザーポインタの移動が少し遅れる!?
次の記事の
の機能を搭載すると、主人公が体を動かして照準を定める時にレーザーポインタが銃より少し遅れて移動しているように見えます。
上の画像のように銃の向きとレーザーポインタの出ている位置が少しズレています。
これは体の向きを変更するUpdateとレーザーポインタの点の位置を指定するUpdateのタイミングのせいで起きてると思われます。
そんなに問題があるわけではないですが、見た目的におかしいので修正します。
体の向きを変更するUpdateが終わった後にレーザーポインタの点の位置を指定すればいいので、Updateの部分をLateUpdateに変更します。
LateUpdateはUpdateが終了した後に実行されるので、体の向きが確定した後にレーザーポインタの位置を指定するようになり、不具合がなくなります。
これで銃を構えたらレーザーポインタを表示した時の処理、敵にレーザーポインタが当たった時の処理、発砲した時の処理が完成しました。
シューティング機能が出来たが、当たり判定個所が大雑把
細かい所を作りこめばなかなかいい感じに出来上がると思います。結構感動ものです!!
ですが・・・、現状では銃を構えた状態でその場で銃が向いている方向にしか打てません。
銃を構えた状態でも歩けるようにする事も出来ますので(そういう風に作れば)そういう作りにしていろいろな所を狙えるようにするのもいいかもしれません。
また当たっている部分が敵キャラのコライダの接触位置なので空間上になっています。
CharacterColliderの大雑把なコライダで判定している為にそうなってしまいます。
それは次回以降、部位に当たり判定個所(コライダ)を作成し、より細かく当たり判定が出来るようにしたいと思います。
銃を構えた状態で歩いて狙いを定めるのも魅力ですが、バイオハザード4やバイオハザード5あたりの構えた状態で狙いを定めるというのを次回から作ってみようと思います。