Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

殺死玩家

我們可以通過跳到敵人身上來殺死他們,但玩家仍然不能死亡。讓我們來解決這個問題。

我們希望偵測到被敵人擊中與壓扁敵人時的不同。我們希望玩家在地板上移動時死亡,但如果他們在空中,則不會死亡。我們可以使用向量數學來區分這兩種碰撞。但是,我們將使用 Area3D 節點,該節點適用於命中框。

使用 Area 節點製作攻擊框

回到 player.tscn 場景,新增一個子節點 Area3D,命名為 MobDetector。接著再新增一個 CollisionShape3D 作為它的子節點。

image0

在*屬性檢視器*中,給它指定一個圓柱體形狀。

image1

這裡有一個技巧,你可以用它來使碰撞只發生在玩家在地面上或靠近地面時。您可以降低圓柱體的高度並將其向上移動到角色的頂部。這樣,當玩家跳躍時,形狀會太高,敵人無法與之碰撞。

image2

你還希望圓柱體比球體更寬。這樣一來,玩家在碰撞之前就會被擊中,並被推到怪物的碰撞盒之上。

圓柱體越寬,玩家就越容易被殺死。

接下來,再次選擇 MobDetector 節點,並在*屬性檢視器*中, 關閉Monitorable 屬性。這使得其他物理節點無法偵測到這個區域。補充的 Monitoring 屬性允許它偵測碰撞。然後,清除 Collision -> Layer,並將遮罩設定為“enemies”層。

image3

When areas detect a collision, they emit signals. We're going to connect one to the Player node. Select MobDetector and go to the Signals dock, double-click the body_entered signal and connect it to the Player

image4

當一個 CharacterBody3DRigidBody3D 節點進入它時,MobDetector 將發出 body_entered 訊號。由於它只遮罩了“enemies”實體層,它將只偵測 Mob 節點。

從程式碼上看,我們要做兩件事:發出一個訊號,我們以後會用來結束遊戲,並銷毀玩家。我們可以用 die() 函式來包裝這些操作,幫助我們給程式碼貼上描述性標籤。

# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit


# And this function at the bottom.
func die():
    hit.emit()
    queue_free()


func _on_mob_detector_body_entered(body):
    die()

執行遊戲

我們可以利用 ''Player'' 的 ''hit'' 信號來結束遊戲。我們所要做的就是將它連接到 ''Main'' 節點上,在處理時停止 ''MobTimer''。

Open main.tscn, select the Player node, and in the Signals dock, connect its hit signal to the Main node.

image5

_on_player_hit() 函式中獲取並停止計時器。

func _on_player_hit():
    $MobTimer.stop()

如果你現在試玩遊戲,你死亡後就會停止刷怪,現有的怪物會離開螢幕。

另請注意,當玩家死亡時,遊戲不再崩潰或顯示錯誤訊息了。 這是因為我們停止了 MobTimer,它不再觸發 _on_mob_timer_timeout() 函式。

另外請注意,敵人與玩家碰撞並死亡取決於 ''Player'' 和 ''Mob'' 的碰撞形狀的大小和位置。你可能需要移動它們,調整它們的大小,以達到緊湊的遊戲感覺。

你可以拍拍自己的背了:你已經完成了整個 3D 遊戲的原型,即使它還有些粗糙。

在此基礎上,我們將會新增計分、重啟遊戲的選項,你還會看到如何使用簡單的動畫讓遊戲變得更加活靈活現。

設定/移除中斷點

這些是 MainMobPlayer 節點的完整腳本,僅供參考。你可以把它們和你的程式碼進行對比檢查。

首先是 main.gd

extends Node

@export var mob_scene: PackedScene


func _on_mob_timer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instantiate()

    # Choose a random location on the SpawnPath.
    # We store the reference to the SpawnLocation node.
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    # And give it a random offset.
    mob_spawn_location.progress_ratio = randf()

    var player_position = $Player.position
    mob.initialize(mob_spawn_location.position, player_position)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)

func _on_player_hit():
    $MobTimer.stop()

接下來是 mob.gd 這個檔案。

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18

# Emitted when the player jumped on the mob
signal squashed

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

func squash():
    squashed.emit()
    queue_free() # Destroy this node

最後,最長的腳本,player.gd

extends CharacterBody3D

signal hit

# How fast the player moves in meters per second
@export var speed = 14
# The downward acceleration while in the air, in meters per second squared.
@export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob
# in meters per second.
@export var bounce_impulse = 16

var target_velocity = Vector3.ZERO


func _physics_process(delta):
    # We create a local variable to store the input direction
    var direction = Vector3.ZERO

    # We check for each move input and update the direction accordingly
    if Input.is_action_pressed("move_right"):
        direction.x = direction.x + 1
    if Input.is_action_pressed("move_left"):
        direction.x = direction.x - 1
    if Input.is_action_pressed("move_back"):
        # Notice how we are working with the vector's x and z axes.
        # In 3D, the XZ plane is the ground plane.
        direction.z = direction.z + 1
    if Input.is_action_pressed("move_forward"):
        direction.z = direction.z - 1

    # Prevent diagonal moving fast af
    if direction != Vector3.ZERO:
        direction = direction.normalized()
        # Setting the basis property will affect the rotation of the node.
        $Pivot.basis = Basis.looking_at(direction)

    # Ground Velocity
    target_velocity.x = direction.x * speed
    target_velocity.z = direction.z * speed

    # Vertical Velocity
    if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    # Iterate through all collisions that occurred this frame
    # in C this would be for(int i = 0; i < collisions.Count; i++)
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If the collision is with ground
        if collision.get_collider() == null:
            continue

        # If the collider is with a mob
        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            # we check that we are hitting it from above.
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                target_velocity.y = bounce_impulse
                # Prevent further duplicate calls.
                break

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

# And this function at the bottom.
func die():
    hit.emit()
    queue_free()

func _on_mob_detector_body_entered(body):
    die()

在下一節課中我們會新增計分和重試選項,再見。