パート3

パートの概要

このパートでは、弾薬数を与えることでプレイヤーの武器を制限します。また、プレイヤーにリロード機能を提供し、武器が発砲したときにサウンドを追加します。

../../../_images/PartThreeFinished.png

注釈

チュートリアルのこの部分に進む前に、パート2 を終了していると想定しています。パート2 の完成したプロジェクトは、パート3の開始プロジェクトになります

では、始めましょう!

レベル(舞台)の変更

FPSが完全に機能するようになったので、FPSのようなレベルに移行しましょう。

Space_Level.tscn``(``assets/Space_Level_Objects/Space_Level.tscn) および/または Ruins_Level.tscn``( ``assets/Ruin_Level_Objects/Ruins_Level.tscn)を開きます。

Space_Level.tscn および Ruins_Level.tscn は、このチュートリアルのために作成された完全なカスタムFPSレベルです。 F6 を押して開いているシーンを実行するか、シーンを実行ボタン を押して、それぞれを試してください。

警告

Space_Level.tscnRuins_Level.tscn よりもグラフィカルにGPUを要求します。コンピューターが Space_Level.tscn のレンダリングに苦労している場合は、代わりに Ruins_Level.tscn を使用してみてください。

レベル全体にいくつかの RigidBody ノードが配置されていることに気づいたかもしれません。それらに RigidBody_hit_test.gd を配置すると、弾丸が当たったことに反応するので、そうしてみましょう!

使用するシーンのいずれか(または両方)について、以下の指示に従ってください

Expand "Other_Objects" and then expand "Physics_Objects".

Expand one of the "Barrel_Group" nodes and then select "Barrel_Rigid_Body" and open it using
the "Open in Editor" button.
This will bring you to the "Barrel_Rigid_Body" scene. From there, select the root node and
scroll the inspector down to the bottom.
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
"RigidBody_hit_test.gd" and select "Open".

Return back to "Space_Level.tscn".

Expand one of the "Box_Group" nodes and then select "Crate_Rigid_Body" and open it using the
"Open in Editor" button.
This will bring you to the "Crate_Rigid_Body" scene. From there, select the root node and
scroll the inspector down to the bottom.
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
"RigidBody_hit_test.gd" and select "Open".

Return to "Space_Level.tscn".
Expand "Misc_Objects" and then expand "Physics_Objects".

Select all the "Stone_Cube" RigidBodies and then in the inspector scroll down to the bottom.
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
"RigidBody_hit_test.gd" and select "Open".

Return to "Ruins_Level.tscn".

これで、いずれかのレベルのすべての剛体に発射でき、弾丸がそれらに衝突したときに反応します!

弾薬数を追加する

プレイヤーに有効な銃があるので、弾薬の量を制限してみましょう。

まず、各武器スクリプトでいくつかの変数を定義する必要があります。

Weapon_Pistol.gd を開き、次のクラス変数を追加します:

var ammo_in_weapon = 10
var spare_ammo = 20
const AMMO_IN_MAG = 10
  • ammo_in_weapon: 現在ピストルにある弾薬の量
  • spare_ammo: ピストル用に残しておいた弾薬の量
  • AMMO_IN_MAG: 完全にリロードされた武器/マガジンの弾薬の量

あとは、1行のコードを fire_weapon に追加するだけです。

Clone.BULLET_DAMAGE = DAMAGE の下に次を追加します ammo_in_weapon-= 1

これにより、プレイヤーが発砲するたびに ammo_in_weapon から1つ削除されます。プレイヤーが fire_weapon に十分な弾薬があるかどうかを確認していないことに注意してください。代わりに、プレイヤーの Player.gd で十分な弾薬があるかどうかを確認します。


次に、ライフルとナイフの両方に弾薬数を追加する必要があります。

注釈

ナイフは弾薬を消費しないのに、なぜナイフに弾薬数を追加するのか疑問に思われるかもしれません。ナイフに弾薬を追加したい理由は、すべての武器に一貫したインターフェースを持たせるためです。

ナイフの弾薬変数を追加しなかった場合、ナイフのチェックを追加する必要があります。弾薬変数をナイフに追加することにより、すべての武器が同じ変数を持っているかどうかを心配する必要がなくなります。

次のクラス変数を Weapon_Rifle.gd に追加します:

var ammo_in_weapon = 50
var spare_ammo = 100
const AMMO_IN_MAG = 50

そして、次を fire_weapon``に追加します: ``ammo_in_weapon -= 1。プレイヤーが何かを打ったかどうかに関係なくプレイヤーが弾薬を失うように、ammo_in_weapon -= 1if ray.is_colliding() チェックの外にあることを確認してください。

今残っているのはナイフだけです。 以下を Weapon_Knife.gd に追加します:

var ammo_in_weapon = 1
var spare_ammo = 1
const AMMO_IN_MAG = 1

ナイフは弾薬を消費しないため、追加する必要があるのはこれだけです。


ここで、Player.gd の一点を変更する必要があります。つまり、

process_input で武器を発射する方法です。 武器を発射するためのコードを次のように変更します:

# ----------------------------------
# Firing the weapons
if Input.is_action_pressed("fire"):
    if changing_weapon == false:
        var current_weapon = weapons[current_weapon_name]
        if current_weapon != null:
            if current_weapon.ammo_in_weapon > 0:
                if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME:
                    animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)
# ----------------------------------

これで武器の弾薬量が制限され、プレイヤーが使い果たすと発砲が停止します。


プレイヤーが弾薬の残量を確認できると理想的です。process_UI という新しい関数を作成しましょう。

最初に、process_UI(delta)_physics_process に追加します。

以下を Player.gd に追加します:

func process_UI(delta):
    if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
        UI_status_label.text = "HEALTH: " + str(health)
    else:
        var current_weapon = weapons[current_weapon_name]
        UI_status_label.text = "HEALTH: " + str(health) + \
                "\nAMMO: " + str(current_weapon.ammo_in_weapon) + "/" + str(current_weapon.spare_ammo)

何が起こっているのか見てみましょう:

まず、現在の武器が UNARMED または KNIFE であるかどうかを確認します。 そうであれば、UNARMEDKNIFE は弾薬を消費しないため、UI_status_label のテキストを変更してプレイヤーの健康状態のみを表示します。

プレイヤーが弾薬を消費する武器を使用している場合、最初に武器ノードを取得します。

次に、プレイヤーの健康状態と、プレイヤーが武器に持っている弾薬の量と、プレイヤーがその武器のために持っている予備弾薬の量を表示するために UI_status_label のテキストを変更します。

これで、プレイヤーがHUDを通じてどれだけの弾薬を持っているかを確認できます。

武器へのリロードの追加

プレイヤーが弾薬を使い果たすことができるようになったので、プレイヤーにそれらを補充させる方法が必要です。 次にリロードを追加しましょう!

リロードのために、すべての武器にいくつかの変数と関数を追加する必要があります。

Weapon_Pistol.gd を開き、次のクラス変数を追加します:

const CAN_RELOAD = true
const CAN_REFILL = true

const RELOADING_ANIM_NAME = "Pistol_reload"
  • CAN_RELOAD: この武器にリロード機能があるかどうかを追跡するブール値
  • CAN_REFILL: この武器の予備弾薬を補充できるかどうかを追跡するブール値。 このパートでは CAN_REFILL を使用しませんが、次のパートでは使用します!
  • RELOADING_ANIM_NAME: この武器のリロードアニメーションの名前。

次に、リロードを処理するための関数を追加する必要があります。 次の関数を Weapon_Pistol.gd に追加します:

func reload_weapon():
    var can_reload = false

    if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
        can_reload = true

    if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
        can_reload = false

    if can_reload == true:
        var ammo_needed = AMMO_IN_MAG - ammo_in_weapon

        if spare_ammo >= ammo_needed:
            spare_ammo -= ammo_needed
            ammo_in_weapon = AMMO_IN_MAG
        else:
            ammo_in_weapon += spare_ammo
            spare_ammo = 0

        player_node.animation_manager.set_animation(RELOADING_ANIM_NAME)

        return true

    return false

何が起こっているのか見てみましょう:

最初に変数を定義して、この武器がリロードできるかどうかを確認します。

次に、プレイヤーがこの武器のアイドルアニメーション状態にあるかどうかを確認します。これは、プレイヤーが発砲、装備、または装備解除していないときにのみリロードできるようにするためです。

次に、プレイヤーに予備の弾薬があるかどうか、そしてすでに武器にある弾薬が完全にリロードされた武器に等しいかどうかを確認します。 このようにして、プレイヤーに弾薬がない場合、または武器がすでに弾薬でいっぱいになっている場合に、プレイヤーがリロードできないようにすることができます。

もしもリロードできる場合は、武器のリロードに必要な弾薬の量を計算します。

プレイヤーが武器を満たすのに十分な弾薬を持っている場合、必要な弾薬を spare_ammo から削除し、ammo_in_weapon を完全な武器/弾倉に設定します。

プレイヤーに十分な弾薬がない場合は、spare_ammo に残っているすべての弾薬を追加し、spare_ammo0 に設定します。

次に、この武器のリロードアニメーションを再生し、true を返します。

プレイヤーがリロードできなかった場合は、false を返します。


次に、ライフルにリロードを追加する必要があります。Weapon_Rifle.gd を開き、次のクラス変数を追加します:

const CAN_RELOAD = true
const CAN_REFILL = true

const RELOADING_ANIM_NAME = "Rifle_reload"

これらの変数はピストルとまったく同じですが、 RELOADING_ANIM_NAME がライフルのリロードアニメーションに変更されているだけです。

次に、reload_weaponWeapon_Rifle.gd に追加する必要があります:

func reload_weapon():
    var can_reload = false

    if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
        can_reload = true

    if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
        can_reload = false

    if can_reload == true:
        var ammo_needed = AMMO_IN_MAG - ammo_in_weapon

        if spare_ammo >= ammo_needed:
            spare_ammo -= ammo_needed
            ammo_in_weapon = AMMO_IN_MAG
        else:
            ammo_in_weapon += spare_ammo
            spare_ammo = 0

        player_node.animation_manager.set_animation(RELOADING_ANIM_NAME)

        return true

    return false

このコードは、ピストルのコードとまったく同じです。


武器のために必要な最後の部分は、ナイフに「リロード」を追加することです。 次のクラス変数を Weapon_Knife.gd に追加します:

const CAN_RELOAD = false
const CAN_REFILL = false

const RELOADING_ANIM_NAME = ""

私たちはナイフをリロードも補充もできないため、両方の定数を false に設定します。 また、ナイフにはリロードアニメーションがないため、RELOADING_ANIM_NAME を空の文字列として定義します。

次に reloading_weapon を追加する必要があります:

func reload_weapon():
    return false

ナイフをリロードすることはできないので、常に false を返します。

プレイヤーにリロードを追加する

ここで、Player.gd にいくつかの項目を追加する必要があります。 まず、新しいクラス変数を定義する必要があります:

var reloading_weapon = false
  • reloading_weapon: プレイヤーが現在リロードを試みているかどうかを追跡する変数。

次に、別の関数呼び出しを _physics_process に追加する必要があります。

process_reloading(delta)_physics_process に追加します。_physics_process は次のようになります:

func _physics_process(delta):
    process_input(delta)
    process_movement(delta)
    process_changing_weapons(delta)
    process_reloading(delta)
    process_UI(delta)

ここで process_reloading を追加する必要があります。次の関数を Player.gd に追加します:

func process_reloading(delta):
    if reloading_weapon == true:
        var current_weapon = weapons[current_weapon_name]
        if current_weapon != null:
            current_weapon.reload_weapon()
        reloading_weapon = false

ここで何が起こっているのか見ていきましょう。

まず、プレイヤーがリロードを試みていることを確認します。

プレイヤーがリロードしようとしている場合、現在の武器を取得します。現在の武器が null ではない場合、その reload_weapon 関数を呼び出します。

注釈

現在の武器が null の場合、現在の武器は UNARMED です。

最後に、プレイヤーが正常にリロードされたかどうかに関係なく、リロードを試み、もう試みる必要がないため、reloading_weaponfalse に設定します。


プレイヤーがリロードできるようにする前に、process_input のいくつかの項目を変更する必要があります。

最初に変更する必要があるのは、武器を変更するためのコードです。プレイヤーがリロードしているかどうかを確認するために、追加のチェック(if reloading_weapon == false:) を追加する必要があります:

if changing_weapon == false:
    # New line of code here!
    if reloading_weapon == false:
        if WEAPON_NUMBER_TO_NAME[weapon_change_number] != current_weapon_name:
            changing_weapon_name = WEAPON_NUMBER_TO_NAME[weapon_change_number]
            changing_weapon = true

これにより、プレイヤーのリロード中に、プレイヤーは武器を変更できなくなります。

ここで、プレイヤーが reload アクションをプッシュしたときにリロードをトリガーするコードを追加する必要があります。次のコードを process_input に追加します:

# ----------------------------------
# Reloading
if reloading_weapon == false:
    if changing_weapon == false:
        if Input.is_action_just_pressed("reload"):
            var current_weapon = weapons[current_weapon_name]
            if current_weapon != null:
                if current_weapon.CAN_RELOAD == true:
                    var current_anim_state = animation_manager.current_state
                    var is_reloading = false
                    for weapon in weapons:
                        var weapon_node = weapons[weapon]
                        if weapon_node != null:
                            if current_anim_state == weapon_node.RELOADING_ANIM_NAME:
                                is_reloading = true
                    if is_reloading == false:
                        reloading_weapon = true
# ----------------------------------

ここで何が起こっているのか見ていきましょう。

まず、プレイヤーが既にリロードしていないこと、またプレイヤーが武器を変更しようとしていないことを確認します。

次に、reload アクションが押されたかどうかを確認します。

プレイヤーが reload を押した場合、現在の武器を取得し、それが null でないことを確認します。次に、CAN_RELOAD 定数を使用して、武器がリロードできるかどうかを確認します。

武器がリロードできる場合、現在のアニメーション状態を取得し、プレイヤーがすでにリロードしているかどうかを追跡するための変数を作成します。

次に、すべての武器を調べて、プレイヤーがその武器のリロードアニメーションをまだプレイしていないことを確認します。

プレイヤーが武器をリロードしていない場合、reloading_weapontrue に設定します。


私が追加したいことの1つは、発射しようとしたときに弾薬がなくなった場合に武器がリロードされる場所です。

また、プレイヤーがリロード中に現在の武器を発砲できないように、ifチェック(is_reloading_weapon == false:)を追加する必要があります。

空の武器を発射しようとするとリロードされるように、process_input の発射コードを変更しましょう:

# ----------------------------------
# Firing the weapons
if Input.is_action_pressed("fire"):
    if reloading_weapon == false:
        if changing_weapon == false:
            var current_weapon = weapons[current_weapon_name]
            if current_weapon != null:
                if current_weapon.ammo_in_weapon > 0:
                    if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME:
                        animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)
                else:
                    reloading_weapon = true
# ----------------------------------

次に、武器を発射する前にプレイヤーがリロードしていないことを確認し、現在の武器に 0 以下の弾薬がある場合、プレイヤーが reloading_weapontrue に設定します発射しようとします。

これにより、プレイヤーは空の武器を発射しようとするときにリロードを試みます。


これが完了すると、プレイヤーはリロードできます!試してみましょう!これで、各武器の予備弾薬をすべて発射できます。

音を追加する

最後に、武器の発射、リロード、交換に伴うサウンドを追加しましょう。

ちなみに

(法的な理由のため)このチュートリアルではゲームサウンドは提供されていません。 https://gamesounds.xyz/ は、「ロイヤルティフリーまたはパブリックドメインの音楽とゲームに適したサウンド」のコレクションです。Sonniss.com GDC 2017 Game Audio Bundle にある Gamemaster's Gun Sound Pack を使用しました。

Simple_Audio_Player.tscn を開きます。それは、単に AudioStreamPlayer を子とする Spatial です。

注釈

これが「シンプルな」オーディオプレーヤーと呼ばれる理由は、パフォーマンスを考慮していないため、またコードが可能な限り簡単な方法でサウンドを提供するように設計されているためです。

3Dオーディオを使用して、3D空間の場所から来ているように聞こえる場合は、AudioStreamPlayer を右クリックし、[型を変更]を選択します。

これにより、ノードブラウザが開きます。AudioStreamPlayer3D に移動し、[変更]を選択します。このチュートリアルのソースでは、AudioStreamPlayer を使用しますが、必要に応じて AudioStreamPlayer3D を使用することもできます。選択したノードに関係なく、以下のコードが機能します。

新しいスクリプトを作成し、Simple_Audio_Player.gd と呼びます。Simple_Audio_Player.tscnSpatial にアタッチし、次のコードを挿入します:

extends Spatial

# All of the audio files.
# You will need to provide your own sound files.
var audio_pistol_shot = preload("res://path_to_your_audio_here")
var audio_gun_cock = preload("res://path_to_your_audio_here")
var audio_rifle_shot = preload("res://path_to_your_audio_here")

var audio_node = null

func _ready():
    audio_node = $Audio_Stream_Player
    audio_node.connect("finished", self, "destroy_self")
    audio_node.stop()


func play_sound(sound_name, position=null):

    if audio_pistol_shot == null or audio_rifle_shot == null or audio_gun_cock == null:
        print ("Audio not set!")
        queue_free()
        return

    if sound_name == "Pistol_shot":
        audio_node.stream = audio_pistol_shot
    elif sound_name == "Rifle_shot":
        audio_node.stream = audio_rifle_shot
    elif sound_name == "Gun_cock":
        audio_node.stream = audio_gun_cock
    else:
        print ("UNKNOWN STREAM")
        queue_free()
        return

    # If you are using an AudioStreamPlayer3D, then uncomment these lines to set the position.
    #if audio_node is AudioStreamPlayer3D:
    #    if position != null:
    #        audio_node.global_transform.origin = position

    audio_node.play()


func destroy_self():
    audio_node.stop()
    queue_free()

ちなみに

play_soundposition のデフォルト値を null に設定することで、オプションの引数にしています。つまり、play_sound を呼び出すときに、必ずしも position を渡す必要はありません。

ここで何が起こっているのかを見てみましょう:


_readyAudioStreamPlayer を取得し、その finished シグナルを destroy_self 関数に接続します。AudioStreamPlayer または AudioStreamPlayer3D ノードのどちらであるかは問題ではありません。両方とも終了シグナルを持っているからです。サウンドが再生されていないことを確認するには、AudioStreamPlayerstop を呼び出します。

警告

サウンドファイルがループするように設定されて**いない**ことを確認してください!ループするように設定されている場合、サウンドは無限に再生され続け、スクリプトは機能しません!

play_sound 関数は Player.gd から呼び出すものです。サウンドが3つの可能なサウンドの1つであるかどうかをチェックし、3つのサウンドの1つである場合、AudioStreamPlayer のオーディオストリームを正しいサウンドに設定します。

不明な音の場合、コンソールにエラーメッセージを出力し、オーディオプレーヤーを解放します。

AudioStreamPlayer3D を使用している場合は、 を削除してオーディオプレーヤーノードの位置を設定し、正しい位置で再生されるようにします。

最後に、AudioStreamPlayer に再生するように指示します。

AudioStreamPlayer がサウンドの再生を終了すると、_readyfinished シグナルを接続してあるので、destroy_self を呼び出します。AudioStreamPlayer を停止し、オーディオプレーヤーを解放してリソースを節約します。

注釈

このシステムは非常に単純で、いくつかの大きな欠陥があります:

欠点の1つは、サウンドを再生するために文字列値を渡す必要があることです。 3つのサウンドの名前を覚えるのは比較的簡単ですが、より多くのサウンドがある場合はますます複雑になる可能性があります。理想的には、これらのサウンドを変数が公開された何らかのコンテナに配置して、再生する各サウンドエフェクトの名前を覚えておく必要がないようにします。

もう1つの欠点は、このシステムではループサウンドエフェクトやバックグラウンドミュージックを簡単に再生できないことです。ループサウンドを再生できないため、足音のような特定のエフェクトは、サウンドエフェクトがあるかどうか、および再生を継続する必要があるかどうかを追跡する必要があるため、達成するのが難しくなります。

このシステムの最大の欠点の1つは、Player.gd からのサウンドしか再生できないことです。理想的には、いつでも任意のスクリプトからサウンドを再生できるようにしたいと考えています。


それが終わったら、再び Player.gd を開きましょう。まず、Simple_Audio_Player.tscn をロードする必要があります。スクリプトのクラス変数セクションに次のコードを配置します:

var simple_audio_player = preload("res://Simple_Audio_Player.tscn")

次に、必要なときに単純なオーディオプレーヤーをインスタンス化してから、その play_sound 関数を呼び出して、再生するサウンドの名前を渡す必要があります。プロセスを簡単にするために、Player.gdcreate_sound 関数を作成しましょう:

func create_sound(sound_name, position=null):
    var audio_clone = simple_audio_player.instance()
    var scene_root = get_tree().root.get_children()[0]
    scene_root.add_child(audio_clone)
    audio_clone.play_sound(sound_name, position)

この関数が何をするかを見てみましょう:


最初の行は Simple_Audio_Player.tscn シーンをインスタンス化し、それを audio_clone という名前の変数に割り当てます。

2行目はシーンのルートを取得しますが、これには大きな(ただし安全な)仮定があります。

最初にこのノードの SceneTree を取得し、次にルートノードにアクセスします。この場合、このノードは Viewport であり、このゲーム全体が実行されています。次に Viewport の最初の子を取得します。この場合、これはたまたま Test_Area.tscn または他の提供されたレベルのルートノードです。ルートノードの最初の子はプレイヤーがいるルートシーンであるという大きな仮定を立てていますが、これは必ずしもそうとは限りません

これがあなたにとって意味をなさない場合、それについてあまり心配しないでください。、一度に複数のシーンがルートノードの子としてロードされている場合にのみ、コードの2行目は確実に動作しません。これは、ほとんどのプロジェクトではまず発生せず、このチュートリアルシリーズでも発生しません。しかしこれは、シーンの読み込みの処理方法によっては潜在的な問題になります。

3行目は、新しく作成された Simple_Audio_Player シーンをシーンルートの子として追加します。これは、弾丸を産出するときとまったく同じように機能します。

最後に、play_sound 関数を呼び出し、create_sound に渡された引数を渡します。これは、渡された引数で Simple_Audio_Player.gdplay_sound 関数を呼び出します。


あとは、必要なときにサウンドを再生するだけです。最初にピストルに音を追加しましょう!

Weapon_Pistol.gd を開きます.

ここで、プレイヤーがピストルを発射したときに音を立てたいので、fire_weapon 関数の最後に以下を追加します:

player_node.create_sound("Pistol_shot", self.global_transform.origin)

プレイヤーがピストルを発射したら、Pistol_shot サウンドを再生します。

プレイヤーがリロードしたときに音を出すには、reload_weapon 関数の player_node.animation_manager.set_animation(RELOADING_ANIM_NAME) の下に以下を追加する必要があります:

player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)

プレイヤーがリロードすると、Gun_cock サウンドが再生されます。


さて、ライフルに音を加えましょう。Weapon_Rifle.gd を開きます。

ライフルが発射されたときに音を鳴らすには、fire_weapon 関数の最後に以下を追加します:

player_node.create_sound("Rifle_shot", ray.global_transform.origin)

プレイヤーがライフルを発射したら、Rifle_shot サウンドを再生します。

プレイヤーがリロードしたときに音を出すには、reload_weapon 関数の player_node.animation_manager.set_animation(RELOADING_ANIM_NAME) の下に以下を追加する必要があります:

player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)

プレイヤーがリロードすると、Gun_cock サウンドが再生されます。

最終ノート

../../../_images/PartThreeFinished.png

今、あなたは発射すると音が鳴る弾薬が制限された武器を手にしています!

この時点で、FPSゲームが動作するための基本はすべて揃っています。追加するとよいと思われるものがまだいくつかあるので、次の3つのパートで追加します!

たとえば、現時点ではスペアに弾薬を追加する方法がないため、最終的には使い果たします。また、RigidBody ノードの外側には撃てるものはありません。

パート4 では、回復アイテムと弾薬のピックアップに加えて、射撃するターゲットを追加します!ジョイパッドのサポートも追加するため、有線のXbox 360コントローラーで遊ぶことができます!

警告

迷子になったら、必ずコードをもう一度読んでください!

このパートの完成したプロジェクトは、ここからダウンロードできます: Godot_FPS_Part_3.zip