Unityで装備している武器をキー操作で切り替えられるようにする機能

前回までで、敵キャラ、主人公の双方の攻撃とダメージの機能が出来ました。

Unityで敵キャラが主人公に近づいた時に攻撃をしてくる機能を作成していきます。
Unityで主人公キャラが剣を振って敵キャラを攻撃出来るようにする機能を作成します

主人公キャラの武器はひとつで固定となっていますが、これを特定のキーを押したら武器を切り替えられるようにしてみます。

初期設定として懐中電灯を持つようにし、キーの1を押すと次の武器の剣に切り替えます。
さらにキーを押すと次の武器または持ち物に変更します。

今回はキーの1を押すごとに順番に武器を切り替える仕様にしています。

今回の機能を作成すると、

↑のように装備している武器を切り替える事が出来るようになります。

今回はとりあえず武器の切り替えを出来るという最低限の機能を追加します。

あらかじめ装備品をヒエラルキー上に置いておくパターンと装備品をプレハブ化し、その都度インスタンス化するパターンを作っていきます。

スポンサーリンク

武器をキャラクターに持たせる

Equip空オブジェクトの子要素に武器を移動

今まで懐中電灯や剣を右手の子要素として設置していましたが、右手の子要素にEquipという空のゲームオブジェクトを作り、そこに合うように懐中電灯と剣の位置、角度を合わせて配置します。

武器の合わせ方は

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

を参考にしてください。

空オブジェクトEquipは右手の子要素に配置

Equipは上のような感じで配置します。

キャラクターは物を持っているようにアニメーションさせていないので、だいたいの位置で合わせます。

物を持って歩いているアニメーションであればさらにわかりやすいと思います。

手等の一部を部分的にアニメーションさせたい場合は

体の一部分だけのアニメーションを変更したい場合はAnimatorControllerのレイヤーとアバターマスクを使う事で実現出来ます。

の記事を参考にしてください。

武器切り替えスクリプトChangeEquipを取りつける

Equipには新しいスクリプトChangeEquipを追加します。publicの値は後で設定します。

Equipは装備を配置する目印なのでMeshとコライダは無効にしておきます。
(間違って3DオブジェクトのCubeでやってしまったのでこうなっています)

ただの空オブジェクトを作成し位置を合わせるだけでOKです。その際Scaleの値は必ず1にしておいてください。

子のTransform(位置情報)は親の位置やScaleの相対値になっていく為、Equipの子要素にも影響が出てしまう為です。

武器を切り替えるスクリプトChageEquip

装備する武器はEquipの子要素に並べておき、インスペクタでweaponsに設定するようにします。

現在装備している武器はweapons配列の何番目かをequipmentで持つようにします。

Startメソッドでは主人公操作スクリプトのMoveと主人公のアニメーションイベント受け取りスクリプトのProcessMyAttackを取得します。

最初の武器のゲームオブジェクトをアクティブにします。

Updateメソッドでは1キーを押した時に主人公がノーマル状態の時だけ武器を変更出来るようにします。

Changeメソッドが実際に武器を切り替えている処理で、まずは配列要素番号を+1した後に、その武器のゲームオブジェクトをアクティブにし、他のゲームオブジェクトを非アクティブにしています。

装備を変えたらProcessMyAttackのSetColliderメソッドで武器のコライダも変更します。

ProcessMyAttackにSetColliderの追加と他のフィールド宣言等を変更します。

capColは元々CapsuleColliderで宣言していましたが、宣言部分でColliderに変更しておきます。

武器によってはCapsuleColliderではなくBoxCollider等を使う場合を考えてこうしておきます。

いわゆる多態性というやつでコライダは色々あるけどみんなコライダだという扱いをします。
太郎や花子など色々な人はいるけれどみんな人間という種族として取り扱うという事、

つまり、お前の物は俺の物、俺の物は俺の物というジャイアン的発想です(全然違う・・・)

これで二つの武器(というかひとつは懐中電灯ですが・・・)の切り替えが出来るようになりました。

スクリプトで武器をインスタンス化し装備させる方法もある

最初から武器を設定し非アクティブにしておくのではなく、プレハブのインスタンス化をしてEquipの場所に表示するという方法もあります。

これならばヒエラルキー上に武器が何十個も表示される事がなく、スクリプト上でインスタンス化し配置する事が出来ます。

Equipの子要素に配置した懐中電灯や剣をAssetsフォルダにドラッグ&ドロップしプレハブにします。

その後、剣等をキャラクターに正しく持たせたTransformのPosition、Rotation、Scaleの値をメモ帳等に書いておきます。

装備品のTransform情報をメモしておく

これは剣等のプレハブをインスタンス化した時に正確な位置や回転、サイズに合わせる為です。

これが終わったらヒエラルキー上のEquipの子要素の装備品は削除します。

ChangeEquipスクリプトを修正します。

まずはStartメソッドでequipmentに-1を入れます。

次にUpdateメソッドでChangeメソッドではなくInstantiateWeaponメソッドを呼び出すようにします。

最後に装備品をインスタンス化するInstantiateWeaponメソッドです。

今装備している武器があればその武器を削除し、インスペクタでweapons配列に先ほどプレハブ化した装備品をドラッグ&ドロップしておきます。

先ほどはヒエラルキーの装備品をインスペクタで設定しましたが、今回はプレハブ化した装備品を設定する必要があります。

武器をインスタンス化したらその武器のColliderコンポーネント(CapsuleColliderやBoxCollider等)をProcessMyAttackのSetColliderメソッドで設定します。

インスタンス化した武器の親要素はTransformのSetParentメソッドで自身(このスクリプトが設定されているゲームオブジェクト)であるEquipにします。

今回は懐中電灯と剣の2つの武器だけを設定したと想定していて、手動でそれぞれの位置や角度、サイズをインスタンス化した装備品に割り当てます。

ここで設定する数値は先ほどメモ帳に残したデータを記述します。

本来であれば武器のパラメータ等を保持するスクリプトを武器自体に設定しておき、そこからインスタンス化した時の位置や角度、サイズを設定するようにすればいいと思います。

今回は2つだけなので手動で設定しました。

ここら辺は

blenderで作成した服、鎧や足をガードする装備等を作成した後Unityに取り込み、キャラクターが装備を着せかえることが出来るようにします。

↑の服の着せ替えの記事でやっているのでそちらも参考にしてください。

装備を切り替える時に何も装備していない時はコライダの切り替えが出来ないので、ProcessMyAttackスクリプトを修正します。

1キーを押すとEquipの子要素の装備品が削除されたり、インスタンス化され表示されるようになりました。

武器切り替えをした時に武器をインスタンス化したサンプル

これで武器の切り替えが終了しました。

次回は敵に当たったエフェクトを設定してみます。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます

コメント

  1. 衛門 より:

    こんにちは。はじめまして。
    ゲーム製作のためにかめくめ様のブログ参考にさせていただいています。
    ChangeEquipスクリプトのUpdate関数内の
    if(Input.GetKeyDown(“1”) && move.GetState() == Move.MyState.Normal)の部分でお聞きしたいことがあり、Moveスクリプト内にGetState関数がなかったためエラーが発生し主人公のstateがNormalの時に1を押すことによってこれ以降の処理が行われると解釈して、Moveスクリプト内のprivate MyState stateをprivateからpublicに変更し、
    move.state == Move.MyState.Normal
    に変更したところエラーが解除されたため、このままでプログラムを動かしているのですが、処理等うまく通ってるか怪しいのでお時間ありましたらGetState関数内の処理があれば教えていただきたいです。申し訳ありませんがよろしくお願いいたします。

    • こんにちは(^^)/
      ブログを参考にしてくださりありがとうございます。

      この辺りの記事は前の記事に機能を積み上げる形でスクリプトを作っていたんですが、全ての記事をC#へと書き換えた時にスクリプト自体を変更していったので前後でズレが生じてしまっています。

      Moveスクリプトはキャラクター操作スクリプトの名前(今となっては名前の付け方がおかしいですが・・・)で、そこでキャラクターの状態を表す列挙型のMyStateをpublicで宣言しています。

      なのでこの記事内でmove.GetState()で取得しているのは主人公キャラクターの状態です。

      https://gametukurikata.com/program/attacknear

      この記事に少し記載があります。

      Moveスクリプトではstateフィールドにキャラクターの状態を持っており、GetStateメソッドでその状態を返しています。

      MyStateにはキャラクターの作りたい状態を定義していきます。

      MyStateはキャラクターの状態の定数値みたいなもので、それをMyState型のstateフィールドに保持しておくという感じになります。

      その為、キャラクターの状態が変わったらその状態をstateに代入し保持しておく必要があります(上の例ではStartメソッドで初期状態であるNormalを設定)。

      他のスクリプトからMoveスクリプトのGetStateメソッドを呼び出すと、キャラクターの現在の状態を調べることが出来ます。

      例えば以下のようなスクリプトです。

      いいかげんに作ってますがインスペクタでMoveスクリプトを設定し、StartメソッドでMoveスクリプトのGetStateメソッドでキャラクターの状態を調べています。

      ==の左辺はキャラクターの実際の状態で、右辺はMoveスクリプトのMyStateの状態である定数を指定し、キャラクターの状態がその指定した状態かどうかを確認しています。

      概ねこんな感じであると思います・・・・。(-_-)

      • 衛門 より:

        お答えいただきありがとうございます!解決いたしました。
        もう1点ほどお聞きしたいことがあって
        https://gametukurikata.com/program/mystatus
        上記の
        public int GetAttackPower() {
        return power + weaponStatus.GetAttackPower ();
        }

        void OnTriggerEnter(Collider col) {
        if(col.tag == “Enemy”) {
        col.GetComponent ().TakeDamage(myStatus.GetAttackPower ());}

        敵に攻撃が当たるとNullReferenceException: Object reference not set to an instance of an objectのエラーが発生して終了してしまいます・・・。
        かめくめ様のエラー対処記事などを参考にしつつエラー解決を自分である程度行いましたが、治る気配がないので度々申し訳ありませんが、ご返答いただけると助かります・・・。

        • NullReferenceException: Object reference not set to an instance of an objectのエラーはフィールドや変数に参照しているべきものが入っていない為に起こります。

          その為GetAttackPowerメソッドではweaponStatusフィールドに参照を入れていない可能性があります。

          weaponStatusは装備した武器に取り付けてあるWeaponStatusスクリプトの参照を入れますが、それを行っているのがSetEquipメソッドになります。

          SetEquipメソッドをいつ呼んでいるかと言うと武器の切り替えを行った時だと思われます。

          武器の切り替えを行った時の処理でSetEquipメソッドを読んで武器のステータスがweaponStatusに入っているかどうかをDebug.Log等で確認してみてください。

          仮に定数値を返してエラーが出なければweaponStatusに参照値が入っていません。

          ↑のようなコードを書いて問題がなければweaponStatusに装備している武器のWeaponStatusスクリプトの参照値を入れるようにしてみてください。

          敵に攻撃を当てた時の武器に取り付けているAttackSwordスクリプトのOnTriggerEnterではEnemyタグと接触した時に敵に取り付けたEnemyスクリプトのTakeDamageを呼び出しています。

          ここでヌル参照が起きるのは敵にEnemyスクリプトを取り付けていない、またはTakeDamageメソッドを呼び出すときに渡した引数のmyStatus.GetAttackPowerメソッドの呼び出しでmyStatusにキャラクターに取り付けたMyStatusスクリプトの参照が入っていない為に起こっている可能性が大きいです。

          ここでも同じようにmyStatus.GetAttackPower()の部分を定数値に変えて問題が出ないか確認します。

          これで問題が出なければmyStatusにキャラクターのMyStatusスクリプトの参照値が入っていないということで、そこら辺を調べてみてください。

          でもAttackSwordスクリプトのStartメソッドで武器のルート(主人公)のMyStatusを入れているので、キャラクターのルート(一番親)にMyStatusスクリプトを取り付けていない可能性もあります。

          これでも問題が出た場合は武器が当たった敵にEnemyスクリプトを取り付けていない可能性が高いです。

          ここら辺の記事は1記事で完結しているわけではないので、的確にどこで問題が出ているかを指摘するのはちょっと難しいです。(^_^;)

          • 衛門 より:

            ありがとうございます!解決いたしました~
            お返事遅くなり大変申し訳ございませんm(__)m

  2. マヴ より:

    はじめまして、かめくめちゃんさん
    いつもスクリプトを参考にさせてもらっています。
    ありがとうございます。

    ご質問なんですが
    ProcessmyAttackや、別のページにあったスクリプトのSetState
    など一部のメソッド(?)が認識してくれない状態です。
    エラーメッセージには
    error CS0246: The type or namespace name `ProcessMyAttack’ could not be found. Are you missing an assembly reference?
    のような感じで表示されました。
    使ったunityのバージョンは2017.3.1f1と5.6.2f1です。

    誠に忙しい中、申し訳ないのですが・・・
    同じようなエラーメッセージの方もいると思うので
    記事で回答か返答、よろしくお願いいたします。