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.

使用程式碼移動玩家

該輪到編寫程式碼了!我們將使用先前建立的輸入動作來移動角色。

按右鍵 Player 節點,選擇*附加腳本*為其新增一個新腳本。在快顯視窗中,先將*範本*設定為 ,後按下*建立*按鈕 。

image0

先定義類的屬性。我們將定義移動速率(標量)、重力加速度,以及一個我們將用來移動角色的速度(向量)。

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO

這是一個移動物體的常見屬性。 target_velocity 是一個組合了速度和方向的 3D 向量。在這裡,我們將其定義為屬性,因為我們希望在影格之間更新並重用其值。

備註

這些值與二維程式碼完全不同,因為距離以米為單位。在 2D 中,一千個單位(像素)可能只對應於螢幕寬度的一半,而在 3D 中,它是一千米。

那麼來編寫移動的程式碼。首先在 _physics_process() 中使用全域 Input 物件來計算輸入方向向量。

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 += 1
    if Input.is_action_pressed("move_left"):
        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 += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

在這裡,我們將使用 _physics_process() 虛函式進行所有計算。與 _process() 一樣,它允許您每影格更新節點,但它是專門為物理相關程式碼設計的,例如運動學物體或剛體。

也參考

要瞭解更多關於 _process()_physics_process() 之間的區別,見 空閒處理與物理處理

我們首先將一個 direction 變數初始化為 Vector3.ZERO。然後,我們檢查玩家是否正在按下一個或多個 move_* 輸入,並相應地更新向量的 xz 分量。它們對應於地平面的軸。

這四個條件給了我們八個可能性和八個可能的方向。

In case the player presses, say, both W and D simultaneously, the vector will have a length of about 1.4. But if they press a single key, it will have a length of 1. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can call its normalized() method.

#func _physics_process(delta):
    #...

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + direction, Vector3.UP)

在這裡,我們只在方向的長度大於零的情況下對向量進行正規化,因為玩家正在按某個方向鍵。

在這種情況下,我們也會得到 Pivot 節點並呼叫其 look_at() 方法。該方法接受一個全域坐標系中要注視的空間位置和上方向。在這種情況下,我們可以使用 Vector3.UP 常數。

備註

節點的局部座標,如 position,是相對於它們的父節點而言的。全域座標,比如 global_position,是相對於你在視口中可以看到的世界主軸而言的。

在 3D 中,包含節點位置的屬性是 position。加上 direction 之後,我們就得到了離 Player 一米遠的觀察位置。

然後,更新速度。需要分別計算地面速度和下降速度。請確保 tab 縮進,使行在 _physics_process() 函式內部,而不在剛編寫的條件外部。

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...

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

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

如果物體在這一影格中與地板發生了碰撞,那麼 CharacterBody3D.is_on_floor() 函式就會返回 true。這就是為什麼我們只在空中對 Player 施加重力。

For the vertical velocity, we subtract the fall acceleration multiplied by the delta time every frame. This line of code will cause our character to fall in every frame, as long as it is not on or colliding with the floor.

物理引擎只有在運動和碰撞發生的情況下才能偵測到在某一影格中與牆壁、地板或其他物體的相互作用。我們將在後面使用這個屬性來編寫跳躍的程式碼。

在最後一行,我們呼叫了 CharacterBody3D.move_and_slide(),這是 CharacterBody3D 類的一個強大方法,可以讓你順利地移動一個角色。如果它在運動過程中撞到了牆,引擎會試著為你把它進行平滑處理。它使用的是 CharacterBody3D 自帶的*速度*值

這就是你在地面上移動角色所需的所有程式碼。

下面是供參考的完整 Player.gd 程式碼。

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    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)

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

測試玩家的移動

將玩家放在 Main 場景中進行測試,這時,需要先產生實體玩家,然後新增相機。 3D 與 2D 不同,如果沒有新增相機,你將無法看到任何物體。

保存 Player 場景,然後打開 Main 場景。可以點擊編輯器頂部的 Main 分頁切換。

image1

如果場景之前已關閉,請轉到檔案系統欄,按兩下 main.tscn 檔重新打開。

To instantiate the Player, right-click on the Main node and select Instantiate Child Scene.

image2

在快顯視窗中,按兩下 player.tscn ,角色將顯示在視窗的中心。

新增腳本

接下來讓我們來新增相機。就像我們對 Player\ 的 Pivot 所做的那樣,我們將建立一個基本裝備。再次右鍵單擊“Main”節點並選擇*新增子節點*。建立一個新的 Marker3D <class_Marker3D>`,並將其命名為「CameraPivot」。選擇「CameraPivot」並新增一個子節點:ref:Camera3D <class_Camera3D>。你的場景樹應該是這樣的。

image3

請注意在選中 Camera 時,左上角會出現一個*預覽*核取方塊。你可以按一下預覽遊戲中的相機投影視角。

image4

我們要使用 Pivot 來旋轉相機,讓他像被吊車吊起來一樣。讓我們先拆分 3D 視圖,以便在進行自由移動的同時觀察相機拍攝到的內容。

在視口上方的工具列中,按一下*視圖*,然後按一下*2 個視口*。你也可以按 Ctrl + 2`(macOS 上則為 :kbd:`Cmd + 2)。

image11

image5

在下面那個視圖中,選中 Camera3D,然後勾選預覽核取方塊打開相機預覽。

image6

在上面那個視圖中,將相機沿 Z 軸(藍色)移動 19 個單位。

image7

接下來就是關鍵。選中 CameraPivot 並將其圍繞 X 周旋轉 -45 度(使用紅色的圓圈)。你會看到相機就像是被連上了吊車一樣移動。

image8

你可以按 F6 運作場景,然後按方向鍵來移動角色。

image9

因為透視投影的緣故,我們會在角色的周圍看到一些空白區域。在這個遊戲中,我們要使用的是正交投影,從而更好地展示遊戲區域,讓玩家更易於識別距離。

再次選中 Camera,然後在*屬性面板* 中將 *Projection*(投影)設為 *Orthogonal*(正交)、將 *Size*(大小)設為 19。角色現在看起來應該更加扁平,背景應該被地面充滿。

備註

當在 Godot 4 中使用正交相機時,方向陰影的品質取決於相機的 Far 值。Far 越高,相機能夠看到的距離就更遠。然而由於更高的 Far 值會使得陰影渲染必須覆蓋到更遠的距離,這個操作也會導致陰影品質下降。

如果在切換到正交相機後方向陰影看起來變得模糊,請減小相機的 Far 屬性到更低的值,如 100 。請不要將 Far 屬性減小得太多,否則遠處的物體將會開始消失。

image10

測試您的場景,你應該能夠在所有 8 個方向上移動,並且不會在地板上出現故障!

這樣,我們就完成了玩家的移動以及視圖。接下來,我們要來處理怪物。