Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

殺死玩家

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

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

使用 Area 節點製作攻擊框

回到 player.tscn 場景,新增一個新的 Area3D 節點作為它的一個子節點。

image0

在*屬性面板*中,給它指定一個圓柱體形狀。

image1

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

image2

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

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

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

image3

當區域偵測到碰撞時,它們會發出訊號。我們要將一個訊號連接到 Player 節點。在*節點*分頁中,按兩下 body_entered 訊號並將其連接到 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()

F5 再試一下遊戲。如果一切設定正確,角色在被敵人碰到時應該會死亡

var player_position = $Player.position

由於此處沒有 $Player 導致的報告有錯!

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

執行遊戲

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

打開 main.tscn 場景,選中 Player 節點,然後在*節點*面板中把 hit 訊號連接到 Main 節點。

image5

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

func _on_player_hit():
    $MobTimer.stop()

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

你可以鼓勵鼓勵自己了:你做出了完整 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 -90 and +90 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()
        $Pivot.look_at(position + direction, Vector3.UP)

    # 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()

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