Unityでバイオハザード風ガンシューティングの機能を作ってみる

今回からUnityを使ってバイオハザード風ガンシューティングの機能を作っていきたいと思います。

あくまで風なので、バイオハザード2と同じバイオハザード4と同じ、というわけにはいきません。

キャラクターが銃を装備していたら、何か特定のキーを押している間は銃を構え、どこを狙っているかわかりやすい表示(レーザーポインタ)をして、敵がいてレーザーポインタが当たっていて銃を撃つキーを押せば敵にダメージを与えます。

また、銃をかまえている間は上半身を少し動かし狙う角度の調整も出来るようにします。
なんだか盛りだくさんで嫌になりそうですが・・・(^_^;)

今回は銃を装備させ、銃を構えるようにしてみます。

スポンサーリンク

銃の3Dオブジェクトの準備とキャラクターに装備させる

銃はAsset Storeで準備しましょう。

3D モデル→小道具→武器→重火器→[PBR] Makarov- Free Edition

をインポートさせていただきました。

バイオハザード0

キャラクターの右手の子要素にCreate Emptyで空オブジェクトを作り、名前をEquipとしています。
その子要素に銃を持たせます。

バイオ1

上のような感じに置きます。

バイオ2

Scaleを調整して位置と角度を変更します。

銃の親要素であるEquipのTransformのPosition、RotationのX,Y,Zが0、ScaleのX、Y、Zが1である事を必ず確認してください。

ここがそれ以外の値であると子要素である銃のTransformはそこからの相対値になるので、位置や角度を変更するとオブジェクトがゆがみます。

また銃をもっと正しく手の位置、角度と合わせたい時は

Unityでキャラクターに正確に武器を持たせる
Unityでキャラクターの手の位置に正確に武器を持たせるように調整します。なんとなく持たせる位置を設定していた人には朗報かも!?

も参考にしてください。

武器のステータス記憶用のスクリプトWeaponStatusを追加し、銃で殴るという場合も考えてCapsuleColliderを追加しチェックを外しておきます。

主人公の打撃攻撃の作成は

Unityで主人公キャラが敵を攻撃出来るようにする機能
Unityで主人公キャラが剣を振って敵キャラを攻撃出来るようにする機能を作成します

を参考にしてください。

バイオ3

回転をさせる場合は上の部分をクリックして操作すると視覚的にわかりやすいです。

これで銃のインスタンスのチェックを外せばとりあえず装備の切り替えで銃まで切り替わるようになります。
(今まで全部の機能を作って頂いた方限定ですが・・・・)

銃を構えるアニメーションの準備と設定

次に銃を撃つ時のアニメーションを用意してください。

AssetStoreでは見つけられませんでした(無料で)。ちゃんと探せばあると思いますが・・・。

アニメーションの作り方は

blender2.76を使ってUnityで使うアニメーションを作成する
Unityで使用するキャラクター用のアニメーションをBlenderを使って作成していきます。作成方法を学べば自分のゲームに自前のアニメーションを使うことが出来ます。
を参考にして作成してみてください。

それではアニメーターコントローラーに構える状態を作ります。

バイオ4

まずBoolean型のWaitShotというアニメーションパラメータを作成します。

上のようにWaitShotというステートを作り、Idle、Walk、Damageから遷移を繋げ、WaitShotがtrueになったらWaitShotステートに遷移するように条件を加えます。

WaitShotステートからはExitに遷移を繋げ、条件にWaitShotがfalseになった時に設定します。
Exitに遷移が移ると次はデフォルトのステートに移動します。この場合はEntryに流れIdleになります。

これでアニメーションの流れが出来たので、スクリプトでアニメーション操作の処理を足していきます。

銃を構えるアニメーションに遷移させるスクリプトを作成する

構えるアニメーションにする為に押すボタンは「Fire2」に設定されているボタンにします。

Windowsパソコンの場合はデフォルトで右クリックになっています。
右クリックを離した時点で構えをやめるようにします。

これらのボタンの動作の処理はMoveスクリプトでやっている(ご自分のキャラクター操作スクリプト内に銃を構える処理を加えてください)のでそちらに処理を加えていきます。

キー操作の判定をしている個所に右クリックを押した時の処理を加えます。

今回は押しっぱなしの時なのでGetButtonを使っています。
さらに状態State.waitshotをManagementスクリプトに追加しておき通常の攻撃の時は銃を構えていない時、銃を構える時は通常の攻撃中でなく武器のタイプがGunの時という条件を加えます。

この条件を加えないと剣を持っているのに銃を構えるアニメーションを行ってしまうなどの不具合が発生します。

条件を満たしたらShot(後で作ります)という銃を撃った時の処理を行うスクリプトの取得を行います。
そして状態をState.waitshotに変更し、銃を構えるアニメーションに移行させます。

ここまでは銃を構えるアニメーションに遷移させる為の条件です。
実際に銃を撃つ条件に当てはまるには状態がState.waitshotでありなおかつFire1のボタンを押した時になります。

それ以外にFire2(右クリック)が離された時の条件が

の部分で、!Input.GetButton(”Fire2″)でマウスの右ボタンを離した(押していない)という条件になり、状態がState.waitshotでなおかつ武器が銃の時という条件になります。

この条件に当てはまった場合はアニメーターパラメータのWaitShotをfalseにしてアニメーションをIdleに遷移させます。
SetDisableLine()関数は後で作ります。

Walk()関数に行く条件はダメージを受けていない、攻撃中でない、銃を構え中でない時という条件にします。
そうしないと、動かないはずなのに動いてしまいます。

最後が状態が銃を構え中であった時の条件です。
構え中でありさらにFire1に設定されているマウスの左クリックがされたら銃を撃ちます。
正直なところ厳格に条件をしていしていますが、いらない可能性もあります・・・。
テストプレイをしているうちにこうなってしまいました・・・・(^_^;)

shotに保存しているShotスクリプトの関数処理が多く記述していますが、これらは後で作成します。

大まかに言うと、レーザーポインタのOnや音を鳴らす、敵に銃弾があたったかどうか?の処理を行っています。

さきほど紹介したスクリプトは以前の記事で作成したスクリプトを元にしているので、この記事単体ではわからない部分もあると思いますが、
キャラクター移動の条件だったり、キャラクターが銃を構える時の条件を記述しているだけです。

銃のレーザーポインタの作成と銃を発砲した時の光

とりあえずShotスクリプトは後回しにして、次はレーザーポインタと銃を撃った時の光の準備をします。

バイオ5

銃の子要素にMuzzle(銃口)をCubeで作りサイズを調整して合わせます。Z方向青矢印が銃弾の飛んでいく先とします。

見た目はいらないのでMeshRendererのチェックを外し、BoxColliderもチェックを外しておきます。
空オブジェクトにしなかったのは位置を合わせる時にやりやすいからです。

バイオハザード20

MuzzleのZ方向の青矢印を銃弾の飛ばす位置として合わす時に、上の画像のように赤い四角の部分をクリックし、Local表示にして合わせてください。

Local表示にするとそのゲームオブジェクトの向きでX、Y、Zの軸の矢印が表示されるようになります。

MuzzleにAdd ComponetでLineRendererとLight、AudioSourceを追加します。
AudioSourceには発砲時に鳴らす銃声を設定し、PlayOnAwakeのチェックは外してください。

AudioSourceについては

Unityで敵キャラクター出現時に効果音を鳴らしてみる
Unityで敵キャラクターが出現した時に効果音を鳴らし出現した事をわかりやすくする機能を作成します

を参照してください。

LineRendererは設定した点に線を引く為のコンポーネントです。

Muzzleの部分から銃口が向いている先の部分まで軌跡を表示することでレーザーポインタの表現が出来ます。

Lightは発砲した時の光を表現します。

バイオ6

設定は上のような感じにしました。
Lightは光の発生点から広がるPointにしました。

LineRendererに設定しているGunsRayというマテリアルを作成します。
ProjectsタブのAssetsエリアで右クリックしCreateからMaterialを選びます。
RenderingModeをTransparentにしAlbedoの色を変更します。

バイオ7

上のような感じで赤色でAlpha(透明度)を200にします。

バイオ8

Emissionを上のように変更します。
Emissionで光の放射を表します。

LineRendererにマテリアルを設定し、コンポーネントのチェックを外したらレーザーポインタの準備が完了です。
Lightもチェックを外してください。

敵にレーザーポインタが当たった時にわかりやすくする為、シューティングポイントを表示します。
それをシーン上にSphereとして作成します。

バイオ9

Sphereの名前をShotPointという名前に変更し、マテリアルを設定します。

マテリアルはレーザーポインタのマテリアルを作った時と同様の方法で作成します。
AlbedoのAlphaを100にしておきます。
出来たらShotPointにマテリアルを設定します。
ShotPointは最初は見えなくしておきたいので、MeshRendererのチェックを外しておきます。

このShotPointは常にシーン上にあり、敵にレーザーポインタが当たるとそこに移動し、
MeshRendererのチェックをいれるという感じで使用します。

ShotPointはプレハブ化し敵にレーザーポインタが当たった時にインスタンス化するという方法もありますが、
毎回インスタンス化するのは処理に時間がかかりそうなので表示のオン・オフで対応しました。

銃を構えた時の処理スクリプトShotの作成

それでは銃を構えた時の処理を実行するスクリプトShotを作成していきます。

まずは変数宣言

いきなり盛りだくさん・・・・(+_+)
コメントにある通りですが、処理を見ていけばわかると思います。

Start関数内はほとんどが使用するコンポーネントの取得です。

ShotスクリプトはMuzzleに設定するのでMuzzleに設定されているコンポーネントはそのままGetComponentで取得出来ますが、
MyStatus等はShotスクリプトが設定されているオブジェクトの親(主人公のオブジェクト)に設定されているので、transform.rootでいったん親まで戻ってからGetComponentしています。

LayerMask.GetMask(“レイヤーの名前”)

でレイヤーの取得が出来ます。レイヤーはオブジェクトのインスペクタで設定が出来ます。
敵キャラクターのレイヤーをEnemyにし、Terrainで設定した木のオブジェクトをTreeに設定します。
(レイヤーの設定はご自由に)

レイヤーがない場合は作成して追加してください。

バイオ10

レイヤーを変更する時に上のようなウインドウが出る事があります。

これは子要素のレイヤーも変更するかどうか聞かれています。
子要素のレイヤーを変更してしまうと不具合になる場合はNo, This Object Onlyをクリックしてください。

例えば敵キャラクターの子要素に主人公キャラを検知するサーチエリアを設定していた場合、
Yes, Change Childreをクリックして子要素のレイヤーもEnemyにしてしまった場合、銃を撃って当たり判定をする時にサーチエリアの範囲も含めてしまうことになります。

なので、敵キャラクターの場合は子要素のレイヤーは変更しないようNo, This Object Onlyを選んでください。

万が一間違えて変更してしまっても子要素のゲームオブジェクトを選択しレイヤーを変更してあげれば大丈夫です。
ゲームオブジェクトを作成した時はDefaultレイヤーに設定されているので、特にレイヤーを設定する必要がないものはDefaultレイヤーに戻すといいと思います。

では簡単な処理から見ていきましょう。

これらはレーザーポインタの表示・非表示
発砲時の光の表示・非表示
音を鳴らす

という処理です。

銃を撃ったタイミングでこれらの関数を呼び出します。

レーザーポインタを表示させる処理

次にレーザーポインタの処理を加えます。

LineRendererでは複数点を指定して線を引きますので、まずは最初の地点を指定します。

line.SetPosition(0, transform.position);

で最初の地点を指定します。
第1引数が0で最初の地点、1で次の地点となります。
この数を増やしていくと点を繋ぐ線が出来ます。

Rayはレーザー光線のようなもので位置と向きを指定します。

ray.origin = transform.position;

でrayの最初の位置をMuzzleの位置に設定します。

ray.direction = transform.forward;

でrayを飛ばす向きをMuzzleの向きに指定します。
transform.forwardでこのスクリプトが設定されているゲームオブジェクトの前方を取得出来ます。

MuzzleのZの位置を発射させる向きにしたのはこの為です。

RaycastHitはレイを飛ばした方向でぶつかった相手の情報が入ります。

Physics.Raycast(ray, hit, rayRange, LayerMask.GetMask(“Field”, “Tree”))

でrayの位置からrayの方向に線を飛ばします。線の長さはrayRangeです。
最後のレイヤーを指定すると、そのレイヤーに指定されているものだけが対象になります。
今回の場合はField、Treeのレイヤーが対象です。

当たっていたら、その情報がhitに保存されます。
Physics.Raycastはひとつの情報ですが、複数の当たったものの情報がほしい場合はPhysics.RaycastAllを使います。
貫通遠距離武器等はそちらを使うのがよさそうです。

Field、Treeレイヤーの時はShotPointをOffにします。
当たったField、Treeの位置をnearPoint、銃口からヒットした位置までの距離をdistanceに入れておきます。
hitFlagは何かしらに当たった時にtrueを入れます。

次に敵にレーザーポインタが当たった時はShotPointを敵に表示したいので、
Enemyレイヤーの時はShotPointの位置を接触したポイントにし、MeshRendererをOnにします。

ここで銃口とヒットした位置(敵に当たった位置)との距離を計算し、distanceより短い距離だった場合、nearPointに敵に当たった位置を入れます。
(飛ばしたレイが当たった場合にその当たった位置はhit.pointで取得出来ます)

つまり、レイがFieldやTreeにも敵にもあたった場合は一番近い距離にあるものにレーザーポインタの到達点を設定します。

今回Physics.Raycastを使っていますが、Physics.RaycastAllを使ってレイが当たった相手をすべて取得してから
その中で一番近い物を取得した方がいいような気もしますね・・・・(^_^;)

Enemyレイヤーには当たったけれど、敵キャラがすでに死亡判定されていた場合は何もしません。

hitFlagがOnの時、レイにヒットした一番近い位置をLineRendererの到達点に設定します。
それ以外は、どこにも当たっていなかったので、

line.SetPosition(1, ray.origin + ray.direction * rayRange);

Muzzleの位置にMuzzleの位置からレイの長さ分移動した位置を足した地点にLineRendererの到達点を指定します。

これでレーザーポインタの処理は終了です。

レーザーポインタの移動が少し遅れる!?

次の記事の

Unityのゲームでバイオハザード風に照準を合わせられるようにする
Unityのガンシューティングの機能に上半身だけを動かして狙いを敵に定められるようにする機能を追加します
の機能を搭載すると、
主人公が体を動かして照準を定める時にレーザーポインタが銃より少し遅れて移動しているように見えます。

バイオ18

上の画像のように銃の向きとレーザーポインタの出ている位置が少しズレています。
これは体の向きを変更するUpdateとレーザーポインタの点の位置を指定するUpdateのタイミングのせいで起きてると思われます。

そんなに問題があるわけではないですが、見た目的におかしいので修正します。
体の向きを変更するUpdateが終わった後にレーザーポインタの点の位置を指定すればいいので、Updateの部分をLateUpdateに変更します。

LateUpdateはUpdateが終了した後に実行されるので、体の向きが確定した後にレーザーポインタの位置を指定するようになり、不具合がなくなります。

バイオ19

弾が敵に当たっているかどうかの判定

次に銃を構えた状態から発砲した時に敵に当たっているかの判定をする処理を作っていきます。

やっていることはレーザーポインタの作成と同じです。

レイを飛ばしてField、Treeレイヤーと接触しているかどうか判定します。
接触していたらその距離をdistanceに入れておきます。

Enemyのレイヤーと接触していたら敵に当たったと判定し、銃口と敵の距離がdistanceより短い時に敵にNockBack処理をさせます。

バイオ11

これで銃を構えたらレーザーポインタを表示たり、敵にレーザーポインタが当たった時の処理、発砲した時の処理が完成しました。

シューティング機能が出来たが、当たり判定個所が大雑把

細かい所を作りこめばなかなかいい感じに出来上がると思います。結構感動ものです!!

ですが・・・、現状では銃を構えた状態でその場で銃が向いている方向にしか打てません。

銃を構えた状態でも歩けるようにする事も出来ますので(そういう風に作れば)そういう作りにしていろいろな所を狙えるようにするのもいいかもしれません。

また当たっている部分が敵キャラのコライダの接触位置なので空間上になっています。
CharacterColliderの大雑把なコライダで判定している為、そうなってしまいます。

それは次回以降、部位に当たり判定個所(コライダ)を作成し、より細かく当たり判定が出来るようにしたいと思います。

銃を構えた状態から装備を切り替え、また銃に装備品を戻すと前の構えた状態の時のレーザーポインタが消えずに現れてしまいます。

これは装備を切り替えた時にレーザーポインタの非表示をしていなかった為です。
装備を切り替えるスクリプトChangeEquipで

を装備を切り替える前に入れておきます。
これで装備品を切り替え、また銃に変えても前のレーザーポインタは表示されません。

今回は敵をEnemyHit、木をTreeというレイヤーに設定していますが、
建物や岩などを設置した場合、木、建物、岩等に同じレイヤー名を指定し、そのレイヤーを使うようにした方がいいです。

今回は木しか敵以外に判断を必要とするレイヤーがなかったのでTreeレイヤーとしましたが、
建物などにも木と同じ処理をしたい場合はそうした方がいいです。

銃を構えた状態で歩いて狙いを定めるのも魅力ですが、バイオハザード4やバイオハザード5あたりの構えた状態で狙いを定めるというのを次回から作ってみようと思います。

レーザーポインタの不具合を修正する

--以降追記&修正(重要)--

銃のレーザーポインタを照射した時に敵にヒットした時とヒットしていない時でレーザーポインタが大きくブレてしまうという事が起きていました。

レイの到達点を短くしていたのがブレの原因ぽいです。
そこで、Physics.Raycastでヒットするかどうかの判定の距離をshotRangeではなくすべてrayRangeでやるように変更しました。
その中でshotPointの表示をする時にshotRange内であるか再度判定し表示するかどうか決めます。

まったく原因が違いました。すみません。(^_^;)
木、敵、フィールドのレイヤーに当たっていなかった時に

line.SetPosition(1, ray.direction * rayRange);

としていたのが原因でした。

LineRendererでは2点を設定し、そこに線を引きますが、
ray.directionは方向でrayRangeは長さです。これでベクトルは計算できますが、これだと
向いている方向を表すray.direction(Vector3)の値からrayRangeの長さ分遠くに行った地点になり、本来の到達点にはなりません。

到達地点は元々のMuzzleの位置ray.originにベクトルを足した分が本来の到達点になります。なので

line.SetPosition(1, ray.origin + ray.direction * rayRange);

が正しい指定になります。

完全に直っていたと思っていたんですが、テストプレイ中に木と空に照準を合わせていたらブレだしたので、修正が間違っていたんだと認識しました・・・。(^_^;)

--以降さらに追記(2016/03/10)--

ショットエラー1

記事内のスクリプトは修正してありますが、実は以前のスクリプトの組み方だと上の画像のように、敵の後ろに木などがあり、両方にレイがヒットしていると、shotPointが表示されない、レーザーポインタが敵をすり抜ける。

という現象が発生していました。

↑のような条件判定をしていて、木のレイヤーに当たっていたらその時点で敵の判定をしていませんでした。

if文の条件の順番を変えると出来そうな気がしますが、実際は出来ません。

EnemyHitを先に判定させようと思うと、間に木があってもレイは敵に当たっている事になっているからです。

つまり銃口と敵の間に木があっても、敵にshotPointが表示され、レイの到達点も敵になってしまいます。

やはりレーザーポインタの到達点を決定するには、当たっている一番近い位置の取得が必須でした。

これでレーザーポインタと銃の当たり判定は大丈夫かなぁ・・・・。