パート4¶
パートの概要¶
このパートでは、回復アイテムピックアップ、弾薬ピックアップ、プレイヤーが破壊できるターゲット、ジョイパッドのサポート、スクロールホイールで武器を変更する機能を追加します。

では、始めましょう!
ジョイパッド入力を追加する¶
注釈
Godotでは、ゲームコントローラーはジョイパッドと呼ばれます。これには、コンソールコントローラー、ジョイスティック(フライトシミュレーターなど)、ホイール(ドライビングシミュレーターなど)、VRコントローラーなどが含まれます!
まず、プロジェクトの入力マップでいくつかのことを変更する必要があります。プロジェクト設定を開き、[インプットマップ]タブを選択します。
次に、さまざまなアクションにいくつかのジョイパッドボタンを追加する必要があります。プラスアイコンをクリックして、ゲームパッドのボタン
を選択します。

任意のボタンレイアウトを自由に使用できます。選択したデバイスが 0
に設定されていることを確認してください。完成したプロジェクトでは、次のものを使用します:
- movement_sprint:
Device 0, Button 4 (L, L1)
- fire:
Device 0, Button 0 (PS Cross, XBox A, Nintendo B)
- reload:
Device 0, Button 0 (PS Square, XBox X, Nintendo Y)
- flashlight:
Device 0, Button 12 (D-Pad Up)
- shift_weapon_positive:
Device 0, Button 15 (D-Pad Right)
- shift_weapon_negative:
Device 0, Button 14 (D-Pad Left)
- fire_grenade:
Device 0, Button 1 (PS Circle, XBox B, Nintendo A).
注釈
スターターアセットをダウンロードした場合、これらは既に設定されています
入力に満足したら、プロジェクト設定を閉じて保存します。
Player.gd
を開いて、ジョイパッド入力を追加しましょう。
まず、いくつかの新しいクラス変数を定義する必要があります。 以下のクラス変数を Player.gd
に追加してください:
# You may need to adjust depending on the sensitivity of your joypad
var JOYPAD_SENSITIVITY = 2
const JOYPAD_DEADZONE = 0.15
これらのそれぞれが何をするかを見てみましょう:
JOYPAD_SENSITIVITY
: これは、ジョイパッドのジョイスティックがカメラを動かす速度です。JOYPAD_DEADZONE
: ジョイパッドのデッドゾーン。ジョイパッドに応じて調整が必要な場合があります。
注釈
多くのジョイパッドは、特定のポイントを中心にジッタを起こします。これに対抗するために、半径JOYPAD_DEADZONE内の動きは無視します。この動きを無視しないと、カメラが揺れます。
また、JOYPAD_SENSITIVITY
は定数ではなく変数として定義しています。これは後で変更するためです。
これで、ジョイパッド入力の処理を開始する準備ができました!
process_input
で、input_movement_vector = input_movement_vector.normalized()
の直前に次のコードを追加します:
# Add joypad input if one is present
if Input.get_connected_joypads().size() > 0:
var joypad_vec = Vector2(0, 0)
if OS.get_name() == "Windows":
joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
elif OS.get_name() == "X11":
joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
elif OS.get_name() == "OSX":
joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
if joypad_vec.length() < JOYPAD_DEADZONE:
joypad_vec = Vector2(0, 0)
else:
joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
input_movement_vector += joypad_vec
# Add joypad input if one is present
if Input.get_connected_joypads().size() > 0:
var joypad_vec = Vector2(0, 0)
if OS.get_name() == "Windows" or OS.get_name() == "X11":
joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
elif OS.get_name() == "OSX":
joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
if joypad_vec.length() < JOYPAD_DEADZONE:
joypad_vec = Vector2(0, 0)
else:
joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
input_movement_vector += joypad_vec
私たちがやっていることを見てみましょう。
まず、接続されたジョイパッドがあるかどうかを確認します。
ジョイパッドが接続されている場合は、左スティックの軸を上/下/左/右に取得します。有線のXbox 360コントローラーにはOSによって異なるジョイスティック軸のマッピングがあるため、OSに基づいた異なる軸を使用します。
警告
This tutorial assumes you are using a XBox 360 or a PlayStation wired controller. Also, I do not (currently) have access to a Mac computer, so the joystick axes may need changing. If they do, please open a GitHub issue on the Godot documentation repository! Thanks!
次に、ジョイパッドのベクトルの長さが JOYPAD_DEADZONE
半径内にあるかどうかを確認します。もしそうなら、 joypad_vec
を空のVector2に設定します。そうでない場合は、正確なデッドゾーンの計算にスケーリングされたラジアルデッドゾーンを使用します。
注釈
ジョイパッド / コントローラーのデッドゾーンの処理方法について、すべてを説明する素晴らしい記事がここにあります (英語)。
この記事で提供されているスケーリングされた放射状デッドゾーンコードの翻訳バージョンを使用しています。この記事は素晴らしい読み物です。ぜひご覧ください!
最後に、joypad_vec
を input_movement_vector
に追加します。
ちなみに
input_movement_vector
を正規化する方法を覚えていますか?これがその理由です! input_movement_vector
を正規化しない場合、キーボードとジョイパッドの両方で同じ方向に押した場合、プレイヤーはより速く動いてしまいます!
process_view_input
という新しい関数を作成し、次のように追加します:
func process_view_input(delta):
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return
# NOTE: Until some bugs relating to captured mice are fixed, we cannot put the mouse view
# rotation code here. Once the bug(s) are fixed, code for mouse view rotation code will go here!
# ----------------------------------
# Joypad rotation
var joypad_vec = Vector2()
if Input.get_connected_joypads().size() > 0:
if OS.get_name() == "Windows":
joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
elif OS.get_name() == "X11":
joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
elif OS.get_name() == "OSX":
joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
if joypad_vec.length() < JOYPAD_DEADZONE:
joypad_vec = Vector2(0, 0)
else:
joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))
rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))
var camera_rot = rotation_helper.rotation_degrees
camera_rot.x = clamp(camera_rot.x, -70, 70)
rotation_helper.rotation_degrees = camera_rot
# ----------------------------------
func process_view_input(delta):
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return
# NOTE: Until some bugs relating to captured mice are fixed, we cannot put the mouse view
# rotation code here. Once the bug(s) are fixed, code for mouse view rotation code will go here!
# ----------------------------------
# Joypad rotation
var joypad_vec = Vector2()
if Input.get_connected_joypads().size() > 0:
if OS.get_name() == "Windows" or OS.get_name() == "X11":
joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
elif OS.get_name() == "OSX":
joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
if joypad_vec.length() < JOYPAD_DEADZONE:
joypad_vec = Vector2(0, 0)
else:
joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))
rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))
var camera_rot = rotation_helper.rotation_degrees
camera_rot.x = clamp(camera_rot.x, -70, 70)
rotation_helper.rotation_degrees = camera_rot
# ----------------------------------
何が起こっているのか見てみましょう:
まず、マウスモードを確認します。マウスモードが MOUSE_MODE_CAPTURED
ではない場合、関数から戻りたいので、それ以降のコードをスキップします。
次に、joypad_vec
という新しい Vector2 を定義します。これにより、正しいジョイスティックの位置が保持されます。 OSに基づいて、適切なジョイスティックの適切な軸にマップされるように値を設定します。
警告
上で述べたように、私は(現在)Macコンピューターにアクセスできないので、ジョイスティックの軸を変更する必要があるかもしれません。もしそうなら、GodotドキュメントリポジトリでGitHub issueを開いてください!よろしく!
次に、process_input
とまったく同じように、ジョイパッドのデッドゾーンを考慮します。
次に、rotation_helper
とプレイヤーの KinematicBody を joypad_vec
を使用して回転させます。
プレイヤーと rotation_helper
の回転を処理するコードが _input
のコードとまったく同じであることに注意してください。行ったことは、joypad_vec
と JOYPAD_SENSITIVITY
を使用するように値を変更することだけです。
注釈
Windows上ではマウス関連のバグがいくつかあるため、process_view
にマウスの回転をプットできません。 これらのバグが修正されると、これもおそらく process_view_input
にマウスの回転をプットするように更新されます。
最後に、プレイヤーが逆さまに見えないようにカメラの回転を固定します。
最後に、_physics_process
に process_view_input
を追加します。
process_view_input
が _physics_process
に追加されると、ジョイパッドを使用してプレイできるようになります!
注釈
ジョイパッドのトリガーを使用しないことにしたのは、軸の管理をもう少し行う必要があるのと、ショルダーボタンを使用して発砲することが好まれるからです。
トリガーを使って発砲したい場合は、process_input
で発砲方法を変更する必要があります。トリガーの軸の値を取得し、それが特定の値(例えば 0.8
)を超えているかどうかをチェックする必要があります。もしそうなら、fire
アクションが押された時と同じコードを追加します。
マウススクロールホイール入力の追加¶
ピックアップとターゲットの作業を開始する前に、もう1つの入力関連処理を追加しましょう。 マウスのスクロールホイールを使用して武器を変更する機能を追加しましょう。
Player.gd
を開き、以下のクラス変数を追加してください:
var mouse_scroll_value = 0
const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
これらの新しい変数のそれぞれが何をしているのかを見てみましょう:
mouse_scroll_value
: マウスのスクロールホイールの値。MOUSE_SENSITIVITY_SCROLL_WHEEL
: 単一のスクロールアクションがmouse_scroll_valueをどれだけ増加させるか。
では、次を _input
に追加しましょう:
if event is InputEventMouseButton and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
if event.button_index == BUTTON_WHEEL_UP:
mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
elif event.button_index == BUTTON_WHEEL_DOWN:
mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL
mouse_scroll_value = clamp(mouse_scroll_value, 0, WEAPON_NUMBER_TO_NAME.size() - 1)
if changing_weapon == false:
if reloading_weapon == false:
var round_mouse_scroll_value = int(round(mouse_scroll_value))
if WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value] != current_weapon_name:
changing_weapon_name = WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value]
changing_weapon = true
mouse_scroll_value = round_mouse_scroll_value
ここで何が起こっているのかを見てみましょう:
まず、イベントが InputEventMouseButton
イベントであり、マウスモードが MOUSE_MODE_CAPTURED
であるかどうかを確認します。 次に、ボタンのインデックスが BUTTON_WHEEL_UP
インデックスなのか ``BUTTON_WHEEL_DOWN``インデックスなのかを確認します。
イベントのインデックスが実際にボタンホイールインデックスである場合、それが BUTTON_WHEEL_UP
または BUTTON_WHEEL_DOWN
インデックスであるかどうかを確認します。 アップまたはダウンに基づいて、mouse_scroll_value
に MOUSE_SENSITIVITY_SCROLL_WHEEL
を加算または減算します。
次に、マウススクロール値を固定して、選択可能な武器の範囲内に収まるようにします。
次に、プレイヤーが武器を変更しているか、リロードしているかを確認します。 プレイヤーがどちらも実行していない場合、mouse_scroll_value
を丸めて int
にキャストします。
注釈
mouse_scroll_value
を int
にキャストしているので、dictionaryのキーとして使用できます。 floatのままにしておくと、プロジェクトを実行しようとしたときにエラーが発生します。
Next, we check to see if the weapon name at round_mouse_scroll_value
is not equal to the current weapon name using WEAPON_NUMBER_TO_NAME
.
If the weapon is different from the player's current weapon, we assign changing_weapon_name
, set changing_weapon
to true
so the player will change weapons in
process_changing_weapon
, and set mouse_scroll_value
to round_mouse_scroll_value
.
ちなみに
mouse_scroll_value
を丸められたスクロール値に設定しているのは、プレイヤーがマウススクロールホイールを値のちょうど中間に置いておくのを望まないためです。 mouse_scroll_value
を round_mouse_scroll_value
に割り当てることにより、各武器が正確に同じ量のスクロールを行って変更されるようにします。
変更する必要があるもう1つは process_input``です。 武器を変更するためのコードで、\ ``changing_weapon = true
行 の直後に以下を追加します:
mouse_scroll_value = weapon_change_number
これで、キーボード入力でもスクロール値が変更されます。 この変更をしなかった場合、スクロール値が同期しなくなります。 スクロールホイールが同期していない場合、前方または後方へのスクロールは次/最後の武器に移行せず、スクロールホイールで変更された次/最後の武器に移行します。
スクロールホイールを使用して武器を変更できるようになりました! それを旋回させてみてください!
回復アイテムピックアップの追加¶
プレイヤーは体力と弾薬を手に入れたので、理想としてこれらのリソースを補充する方法が必要です。
Health_Pickup.tscn
を開きます。
まだ展開されていない場合は、Holder
を展開します。 2つのSpatialノードがあることに注目してください。1つは Health_Kit
、もう1つは Health_Kit_Small
と呼ばれます。
これは、実際には2つのサイズの回復アイテムピックアップを作成するためです。Health_Kit
および Health_Kit_Small
には、子として単一の MeshInstance のみがあります。
次に、Health_Pickup_Trigger
を展開します。 これは Area ノードであり、プレイヤーが回復キットを拾うのに十分な距離を歩いたかどうかを確認するために使用します。 展開すると、サイズごとに1つずつ、2つのコリジョンシェイプが見つかります。回復アイテムピックアップのサイズに応じて異なるコリジョンシェイプサイズを使用するので、小さい回復アイテムピックアップの方が、そのサイズに近いトリガーコリジョンシェイプを持っています。
最後に注意することは、AnimationPlayer ノードを使用して、回復キットがゆっくりと動き回るようにすることです。
Health_Pickup
を選択し、Health_Pickup.gd
という新しいスクリプトを追加します。 次のように追加します:
extends Spatial
export (int, "full size", "small") var kit_size = 0 setget kit_size_change
# 0 = full size pickup, 1 = small pickup
const HEALTH_AMOUNTS = [70, 30]
const RESPAWN_TIME = 20
var respawn_timer = 0
var is_ready = false
func _ready():
$Holder/Health_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
is_ready = true
kit_size_change_values(0, false)
kit_size_change_values(1, false)
kit_size_change_values(kit_size, true)
func _physics_process(delta):
if respawn_timer > 0:
respawn_timer -= delta
if respawn_timer <= 0:
kit_size_change_values(kit_size, true)
func kit_size_change(value):
if is_ready:
kit_size_change_values(kit_size, false)
kit_size = value
kit_size_change_values(kit_size, true)
else:
kit_size = value
func kit_size_change_values(size, enable):
if size == 0:
$Holder/Health_Pickup_Trigger/Shape_Kit.disabled = !enable
$Holder/Health_Kit.visible = enable
elif size == 1:
$Holder/Health_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
$Holder/Health_Kit_Small.visible = enable
func trigger_body_entered(body):
if body.has_method("add_health"):
body.add_health(HEALTH_AMOUNTS[kit_size])
respawn_timer = RESPAWN_TIME
kit_size_change_values(kit_size, false)
このスクリプトの実行内容をクラス変数から見ていきましょう:
kit_size
: 回復アイテムピックアップのサイズ。setget
関数を使用して、変更されたかどうかを確認していることに注目してください。HEALTH_AMMOUNTS
: 各サイズの各ピックアップに含まれる回復量。RESPAWN_TIME
:回復アイテムピックアップが再産出するのにかかる時間(秒単位)- ``respawn_timer``回復アイテムピックアップが再産出を待機している時間を追跡するために使用される変数。
is_ready
:_ready
関数が呼び出されたかどうかを追跡する変数。
setget
関数は _ready
の前に呼び出されるため、is_ready
を使用しています。_ready
が呼び出されるまで子ノードにアクセスできないため、最初のkit_size_change呼び出しを無視する必要があります。 最初の setget
呼び出しを無視しないと、デバッガでいくつかのエラーが発生します。
また、エクスポートされた変数の使用方法にも注目してください。 これは、エディタで回復アイテムピックアップのサイズを変更できるようにするためです。 これにより、エクスポートされた変数を使用してエディタでサイズを簡単に変更できるため、2つのサイズに対して2つのシーンを作成する必要がなくなります。
ちなみに
GDScriptの基本 を参照し、使用可能なエクスポートヒントのリストについては、GDScriptエクスポートセクションまでスクロールします。
_ready
を見てみましょう:
まず、Health_Pickup_Trigger
からの body_entered
シグナルを trigger_body_entered
関数に接続します。 これにより、Area に入るボディが trigger_body_entered
関数をトリガーします。
次に、set_
関数を使用できるように is_ready
を true
に設定します。
次に、kit_size_change_values
を使用して、すべての可能なキットとそのコリジョンシェイプを非表示にします。 最初の引数はキットのサイズであり、2番目の引数はそのサイズでコリジョンシェイプとメッシュを有効にするか無効にするかです。
次に、選択したキットのサイズのみを表示し、kit_size_change_values
を呼び出して kit_size
と true
を渡すので、kit_size
のサイズが有効になります。
次に、kit_size_change
を見てみましょう。
最初に行うことは、is_ready
が true
であるかどうかを確認することです。
is_ready
が true
の場合、kit_size_change_values
を使用して kit_size
に既に割り当てられているキットを無効にし、kit_size
と false
を渡します。
次に、渡された新しい値 value
を kit_size
に割り当てます。 次に kit_size_change_values
を呼び出して kit_size
を再度渡しますが、今回は2番目の引数を true
にして、有効にします。kit_size
を渡された値に変更したため、渡されたキットのサイズが見えるようになります。
is_ready
が true
でない場合、渡された value
に kit_size
を割り当てるだけです。
それでは、kit_size_change_values
を見てみましょう。
最初に行うのは、渡されたサイズを確認することです。有効/無効にするサイズに基づいて、異なるノードを取得します。
size
に対応するノードのコリジョンシェイプを取得し、引数/変数で渡された enabled
に基づいて無効にします。
注釈
enable
の代わりに !enable
を使用するのはなぜですか? これは、ノードを有効にしたいというときは true
を渡すことができますが、CollisionShape は有効化ではなく無効化という考え方を使っているので、反転する必要があります。 それを反転させることで、コリジョンシェイプを有効にし、true
が渡されたときにメッシュを表示できます。
次に、メッシュを保持する正しい Spatial ノードを取得し、その可視性を enable
に設定します。
この関数は少しわかりにくいかもしれません。 このように考えてみてください: enabled
を使用して size
の適切なノードを有効/無効にします。 これにより、表示されていないサイズの回復アイテムは取得できず、適切なサイズのメッシュのみが表示されます。
最後に、trigger_body_entered
を見てみましょう。
最初に行うことは、入力した本体に add_health
というメソッド/関数があるかどうかを確認することです。 もしそうなら、add_health
を呼び出して、現在のキットサイズによって提供される回復量を渡します。
次に、respawn_timer
を RESPAWN_TIME
に設定し、プレイヤーが再び健康になるまで待機する必要があります。 最後に、kit_size_change_values
を呼び出して、kit_size
と false
を渡して、kit_size
のキットが再産出するのを待つまで、見えないようにします。
プレイヤーがこの回復アイテムピックアップを使用する前に行う必要がある最後の作業は、Player.gd
にいくつかの項目を追加することです。
Player.gd
を開き、次のクラス変数を追加します:
const MAX_HEALTH = 150
MAX_HEALTH
: プレイヤーが持つことができる体力の最大量。
次に、プレイヤーに add_health
関数を追加する必要があります。以下を Player.gd
に追加します:
func add_health(additional_health):
health += additional_health
health = clamp(health, 0, MAX_HEALTH)
これを簡単に説明しましょう。
まず、プレイヤーの現在の体力に additional_health
を追加します。次に、体力をクランプして、MAX_HEALTH
よりも高い値、または 0
よりも低い値を取ることができないようにします。
これが完了すると、プレイヤーは回復アイテムを収集できるようになります!いくつかの Health_Pickup
シーンをいくつか配置して試してみてください。インスタンス化された Health_Pickup
シーンが選択されたときに、便利なドロップダウンからエディタで回復アイテムピックアップのサイズを変更できます。
弾薬ピックアップの追加¶
体力を追加できることはとても良いことですが、(現在)何ものも私たちを傷つけることはできないので、追加してもなにもご褒美を得ることができません。 次に弾薬箱を追加しましょう!
Ammo_Pickup.tscn
を開きます。Health_Pickup.tscn
とまったく同じように構成されていますが、メッシュとトリガーのコリジョンシェイプがメッシュサイズの違いを考慮してわずかに変更されていることに注意してください。
Ammo_Pickup
を選択し、Ammo_Pickup.gd
という新しいスクリプトを追加します。 次のように追加します:
extends Spatial
export (int, "full size", "small") var kit_size = 0 setget kit_size_change
# 0 = full size pickup, 1 = small pickup
const AMMO_AMOUNTS = [4, 1]
const RESPAWN_TIME = 20
var respawn_timer = 0
var is_ready = false
func _ready():
$Holder/Ammo_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
is_ready = true
kit_size_change_values(0, false)
kit_size_change_values(1, false)
kit_size_change_values(kit_size, true)
func _physics_process(delta):
if respawn_timer > 0:
respawn_timer -= delta
if respawn_timer <= 0:
kit_size_change_values(kit_size, true)
func kit_size_change(value):
if is_ready:
kit_size_change_values(kit_size, false)
kit_size = value
kit_size_change_values(kit_size, true)
else:
kit_size = value
func kit_size_change_values(size, enable):
if size == 0:
$Holder/Ammo_Pickup_Trigger/Shape_Kit.disabled = !enable
$Holder/Ammo_Kit.visible = enable
elif size == 1:
$Holder/Ammo_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
$Holder/Ammo_Kit_Small.visible = enable
func trigger_body_entered(body):
if body.has_method("add_ammo"):
body.add_ammo(AMMO_AMOUNTS[kit_size])
respawn_timer = RESPAWN_TIME
kit_size_change_values(kit_size, false)
このコードは、回復アイテムピックアップとほぼ同じに見えることに気付いたかもしれません。 それはほとんど同じだからです! 変更されたのはごく一部であり、それが私たちが検討することです。
まず、HEALTH_AMMOUNTS
から AMMO_AMOUNTS
に変更することに注意してください。AMMO_AMOUNTS
は、ピックアップが現在の武器に追加する弾薬クリップ/マガジンの数です。回復ポイントがいくつ付与されるかを表す HEALTH_AMMOUNTS
の場合とは異なり、生の弾薬量ではなくクリップ全体を現在の武器に加算します)
注目すべき他の唯一のものは trigger_body_entered
にあります。add_health
の代わりに add_ammo
と呼ばれる関数の存在を確認し、呼び出しています。
これら2つの小さな変更を除いて、他のすべては回復アイテムピックアップと同じです!
弾薬のピックアップを機能させるために必要なことは、プレイヤーに新しい関数を追加することだけです。Player.gd
を開き、次の関数を追加します:
func add_ammo(additional_ammo):
if (current_weapon_name != "UNARMED"):
if (weapons[current_weapon_name].CAN_REFILL == true):
weapons[current_weapon_name].spare_ammo += weapons[current_weapon_name].AMMO_IN_MAG * additional_ammo
この関数の動作について説明します。
最初に確認するのは、プレイヤーが UNARMED
かどうかです。UNARMED
にはノード/スクリプトがないため、ノード/スクリプトを current_weapon_name
にアタッチする前に、プレイヤーが UNARMED
になっていないことを確認する必要があります。
次に、現在の武器を補充できるかどうかを確認します。 現在の武器が補充可能であれば、現在の武器の AMMO_IN_MAG
値に追加する弾薬クリップの数(additional_ammo
)を掛けることで、完全なクリップ/弾倉に相当する弾薬を武器に追加します。
これで、追加の弾薬を入手できるはずです! 1つ/両方/すべてのシーンに弾薬箱をいくつか置いて、試してみてください!
注釈
携行できる弾薬の量が制限されていないことに注意してください。 各武器が持ち込める弾薬の量を制限するには、各武器のスクリプトに追加の変数を追加し、add_ammo
で弾薬を追加した後に武器の spare_ammo
変数をクランプする必要があります。
破壊可能なターゲットの追加¶
このパートを終了する前に、いくつかのターゲットを追加してみましょう。
Target.tscn
を開き、シーンツリーのシーンを見てみます。
まず、RigidBody ノードではなく StaticBody ノードを使用していることに注意してください。 この背後にある理由は、壊れていないターゲットがどこにも移動しないことです。RigidBody を使用すると、静止している必要があるため、価値よりも面倒が勝っています。
ちなみに
また、StaticBody を RigidBody に重ねて使用することにより、パフォーマンスを少し改善できます。
もう1つ注意すべきことは、Broken_Target_Holder
というノードがあることです。 このノードは、Broken_Target.tscn
と呼ばれる生成/インスタンス化されたシーンを保持します。Broken_Target.tscn
を開きます。
ターゲットが5つの部分に分割されていることに注目してください。各部分は RigidBody ノードです。 ターゲットがあまりにも多くのダメージを受け、破壊する必要がある場合、このシーンを産出/インスタンスします。 次に、壊れていないターゲットを非表示にします。そのため、粉々になったターゲットが産出/インスタンス化されたのではなく、粉々になったターゲットのように見えます。
Broken_Target.tscn
を開いたまま、すべての RigidBody ノードに RigidBody_hit_test.gd
をアタッチします。これにより、プレイヤーは壊れた破片を撃つことができ、それは弾丸に反応します。
さて、Target.tscn
に戻り、Target
StaticBody ノードを選択して、Target.gd
という新しいスクリプトを作成します。
次のコードを Target.gd
に追加します:
extends StaticBody
const TARGET_HEALTH = 40
var current_health = 40
var broken_target_holder
# The collision shape for the target.
# NOTE: this is for the whole target, not the pieces of the target.
var target_collision_shape
const TARGET_RESPAWN_TIME = 14
var target_respawn_timer = 0
export (PackedScene) var destroyed_target
func _ready():
broken_target_holder = get_parent().get_node("Broken_Target_Holder")
target_collision_shape = $Collision_Shape
func _physics_process(delta):
if target_respawn_timer > 0:
target_respawn_timer -= delta
if target_respawn_timer <= 0:
for child in broken_target_holder.get_children():
child.queue_free()
target_collision_shape.disabled = false
visible = true
current_health = TARGET_HEALTH
func bullet_hit(damage, bullet_transform):
current_health -= damage
if current_health <= 0:
var clone = destroyed_target.instance()
broken_target_holder.add_child(clone)
for rigid in clone.get_children():
if rigid is RigidBody:
var center_in_rigid_space = broken_target_holder.global_transform.origin - rigid.global_transform.origin
var direction = (rigid.transform.origin - center_in_rigid_space).normalized()
# Apply the impulse with some additional force (I find 12 works nicely).
rigid.apply_impulse(center_in_rigid_space, direction * 12 * damage)
target_respawn_timer = TARGET_RESPAWN_TIME
target_collision_shape.disabled = true
visible = false
このスクリプトの動作について、クラス変数から説明します:
TARGET_HEALTH
: 完全に回復したターゲットを破壊するために必要なダメージの量。current_health
: このターゲットが現在持っている体力の量。broken_target_holder
: 簡単に使用できるようにBroken_Target_Holder
ノードを保持する変数。target_collision_shape
: 壊れていないターゲットの CollisionShape を保持する変数。TARGET_RESPAWN_TIME
: ターゲットが再出現するのにかかる時間の長さ(秒単位)。target_respawn_timer
: ターゲットが壊れている時間を追跡する変数。destroyed_target
: 壊れたターゲットシーンを保持するための PackedScene。
preload
を使用する代わりに、エクスポートされた変数(PackedScene)を使用して破損したターゲットシーンを取得する方法に注意してください。エクスポートされた変数を使用すると、エディタからシーンを選択できます。別のシーンを使用する必要がある場合は、エディタで別のシーンを選択するのと同じくらい簡単です。 使用しているシーンを変更するためにコードに移動する必要はありません。
_ready
を見てみましょう。
最初に行うことは、壊れたターゲットホルダーを取得し、それを broken_target_holder
に割り当てることです。 ここでは、$
ではなく get_parent().get_node()
を使用していることに注意してください。$ `` を使用する場合は、\ ``get_parent().get_node()
を $"../Broken_Target_Holder"
に変更する必要があります。
注釈
これが書かれた時点では、親ノードを取得するために $
を使った $"../ NodeName"
が使用できることに気づきませんでした。これが、get_parent().get_node()
が代わりに使用されている理由です。
次に、コリジョンシェイプを取得し、target_collision_shape
に割り当てます。 コリジョンシェイプが必要な理由は、メッシュが見えなくても、コリジョンシェイプが物理世界に存在するためです。 これにより、プレイヤーは見えないにもかかわらず、壊れていないターゲットとやり取りできるようになります。 これを回避するには、メッシュを表示/非表示にするときにコリジョンシェイプを無効/有効にします。
次に、_physics_process
を見てみましょう。
再産出には _physics_process
のみを使用するため、最初に行うことは、target_respawn_timer
が 0
より大きいかどうかを確認することです。
もしそうなら、それから delta
を減算します。
次に、target_respawn_timer
が 0
以下かどうかを確認します。 この背後にある理由は、target_respawn_timer
から delta
を削除し、それが 0
以下の場合は、ターゲットがここに到着したので、タイマーが終了したときに行うべきことは何でも効果的に実行できます。
この場合、ターゲットを再産出します。
最初に行うことは、壊れたターゲットホルダーのすべての子を削除することです。 これを行うには、broken_target_holder
内のすべての子を反復処理し、queue_free
を使用してそれらを解放します。
次に、disabled
ブール値を false
に設定してコリジョンシェイプを有効にします。
次に、ターゲットとそのすべての子ノードを再び表示します。
最後に、ターゲットの体力(current_health
)を TARGET_HEALTH
にリセットします。
最後に、bullet_hit
を見てみましょう。
最初に行うことは、ターゲットの体力から弾丸が与えるダメージを差し引くことです。
次に、ターゲットの体力が 0
以下であるかどうかを確認します。 もしそうなら、ターゲットは死んだばかりで、壊れたターゲットを生成する必要があります。
まず、破壊された新しいターゲットシーンをインスタンス化し、それを新しい変数 clone
に割り当てます。
次に、壊れたターゲットホルダーの子として clone
を追加します。
ボーナス効果のために、すべてのターゲットピースを外側に爆発させます。 これを行うために、clone
のすべての子を反復処理します。
各子について、最初に RigidBody ノードかどうかを確認します。 そうである場合、次に、子ノードを基準にしてターゲットの中心位置を計算します。 次に、子ノードが中心を基準とする方向を把握します。 これらの計算された変数を使用して、弾丸の損傷を力として使用して、計算された中心から中心から離れる方向に子を押します。
注釈
ダメージに 12
を掛けると、より劇的な効果が得られます。 ターゲットをどの程度爆発的に粉砕したいかに応じて、これを高い値または低い値に変更できます。
次に、ターゲットの再産出タイマーを設定します。 タイマーを TARGET_RESPAWN_TIME
に設定しているため、再生成されるまで TARGET_RESPAWN_TIME
数がかかります。
次に、壊れていないターゲットのコリジョンシェイプを無効にし、ターゲットの可視性を false
に設定します。
警告
エディタで Target.tscn
にエクスポートされた destroyed_target
値を設定してください! そうしないと、ターゲットは破壊されず、エラーが発生します!
それが完了したら、1つ/両方/すべてのレベルに Target.tscn
インスタンスをいくつか配置します。 十分なダメージを受けた後、それらが5つの破片に爆発することに気付くはずです。 しばらくすると、再びターゲット全体に再出現します。