国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Godot引擎 4.0 文檔 - 第一個 3D 游戲

這篇具有很好參考價值的文章主要介紹了Godot引擎 4.0 文檔 - 第一個 3D 游戲。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

本文為Google Translate英譯中結(jié)果,DrGraph在此基礎(chǔ)上加了一些校正。英文原版頁面:

Your first 3D game — Godot Engine (stable) documentation in English

你的第一個 3D 游戲?

在這個循序漸進的教程系列中,您將使用 Godot 創(chuàng)建您的第一個完整的 3D 游戲。到本系列結(jié)束時,您將擁有自己的一個簡單但已完成的項目,就像下面的動畫 gif 一樣。

godot文檔,godot,3d,游戲

我們將在此處編寫的游戲類似于您的第一個 2D 游戲,但有一點不同:您現(xiàn)在可以跳躍,您的目標是壓扁小兵。這樣,您既可以識別在上一教程中學到的模式,又可以使用新代碼和功能在這些模式的基礎(chǔ)上進行構(gòu)建。

您將學習:

  • 使用跳躍機制處理 3D 坐標。

  • 使用運動體移動 3D 角色并檢測它們何時以及如何發(fā)生碰撞。

  • 使用物理層和一個組來檢測與特定實體的交互。

  • 通過定期實例化怪物來編寫基本的程序游戲。

  • 設(shè)計一個運動動畫并在運行時改變它的速度。

  • 在 3D 游戲上繪制用戶界面。

以及更多。

本教程適用于學習了完整入門系列的初學者?!鹃_始時我們會慢一些,以進行詳細說明】,并在后續(xù)執(zhí)行類似步驟時簡要一些。如果您是一位經(jīng)驗豐富的程序員,您可以在此處瀏覽完整演示的源代碼:Squash the Creep 源代碼。

注:您可以在不完成 2D 系列的情況下關(guān)注本系列。但是,如果您不熟悉游戲開發(fā),我們建議您從 2D 開始。3D 游戲代碼總是更復雜,而 2D 系列將為您提供更舒適的基礎(chǔ)。

我們準備了一些游戲資源,以便我們可以直接跳轉(zhuǎn)到代碼。您可以在這里下載它們:Squash the Creeps assets。

我們將首先為玩家的動作制作一個基本原型。然后我們將添加我們將在屏幕周圍隨機生成的怪物。之后,我們將在用一些漂亮的動畫改進游戲之前實現(xiàn)跳躍和擠壓機制。我們將以得分和重試屏幕結(jié)束。

設(shè)置游戲區(qū)?

在第一部分中,我們將設(shè)置游戲區(qū)域。讓我們從導入開始資源和設(shè)置游戲場景開始吧。

我們已經(jīng)準備了一個 Godot 項目,其中包含我們將在本教程中使用的 3D 模型和聲音,鏈接在索引頁面中。如果您還沒有這樣做,您可以在此處下載存檔:Squash the Creeps assets。

下載后,將 .zip 存檔解壓縮到您的計算機上。打開 Godot 項目管理器并單擊導入按鈕。

godot文檔,godot,3d,游戲

在導入彈出窗口中,輸入新創(chuàng)建目錄的完整路徑?squash_the_creeps_start/。您可以單擊右側(cè)的瀏覽按鈕打開文件瀏覽器并導航到文件夾包含的project.godot文件。

godot文檔,godot,3d,游戲

單擊導入和編輯【Import & Edit以在編輯器中打開項目。

godot文檔,godot,3d,游戲

啟動項目包含一個圖標和兩個文件夾:art/fonts/。在那里,您會找到我們將在游戲中使用的美術(shù)資源和音樂。

godot文檔,godot,3d,游戲

有兩個 3D 模型,player.glbmob.glb,屬于這些模型的一些材料,以及一個音樂曲目。

設(shè)置可玩區(qū)域?

我們將以普通節(jié)點作為其根來創(chuàng)建我們的主場景。在?Scene?dock 中,單擊左上角“+”圖標表示的Add Child Node按鈕,然后雙擊Node。命名節(jié)點Main。重命名節(jié)點的另一種方法是右鍵單擊節(jié)點并選擇重命名(或F2)?;蛘?,要將節(jié)點添加到場景,您可以按Ctrl?+?a(或在 macOS 上Cmd?+?a)。

godot文檔,godot,3d,游戲

按Ctrl?+?s(在 macOS 上Cmd?+?s)將場景另存為main.tscn。

我們將從添加防止角色掉落的地板開始。要創(chuàng)建地板、墻壁或天花板等靜態(tài)碰撞體,您可以使用StaticBody3D節(jié)點。它們需要CollisionShape3D子節(jié)點來定義碰撞區(qū)域。選擇Main節(jié)點后,添加一個StaticBody3D?節(jié)點,然后添加一個CollisionShape3D。將StaticBody3D重命名為Ground.

godot文檔,godot,3d,游戲

你的場景樹應該是這樣的

godot文檔,godot,3d,游戲

CollisionShape3D旁邊會出現(xiàn)一個警告標志,因為我們還沒有定義它的形狀。如果單擊該圖標,將出現(xiàn)一個彈出窗口,為您提供更多信息。

godot文檔,godot,3d,游戲

要創(chuàng)建形狀,請選擇CollisionShape3D節(jié)點,前往Inspector?并單擊Shape屬性旁邊的<empty>字段。創(chuàng)建一個新的BoxShape3D。

godot文檔,godot,3d,游戲

盒子形狀非常適合平坦的地面和墻壁。它的厚度使其能夠可靠地阻擋快速移動的物體。

一個盒子的線框出現(xiàn)在視口中,帶有三個橙色點。您可以單擊并拖動它們以交互方式編輯形狀的范圍。我們還可以在檢查器中精確設(shè)置尺寸。單擊BoxShape3D以展開資源。將其大小設(shè)置為X 軸60、Y 軸2和Z 軸60

godot文檔,godot,3d,游戲

碰撞形狀是不可見的。我們需要添加一個與之配套的視覺地板。選擇該Ground節(jié)點并添加一個MeshInstance3D作為其子節(jié)點。

godot文檔,godot,3d,游戲

Inspector中,單擊Mesh旁邊的字段并創(chuàng)建一個BoxMesh?資源以創(chuàng)建一個可見的框。

godot文檔,godot,3d,游戲

再一次,默認情況下它太小了。單擊方框圖標展開資源并將其大小設(shè)置為60260。

godot文檔,godot,3d,游戲

您應該會在視口中看到一個覆蓋網(wǎng)格以及藍色和紅色軸的寬灰色平板。

我們要把地面向下移動,以便我們可以看到地板網(wǎng)格。選擇?Ground節(jié)點,按住Ctrl鍵打開網(wǎng)格捕捉,然后單擊并向下拖動 Y 軸。它是移動小工具中的綠色箭頭。

godot文檔,godot,3d,游戲

注:如果您看不到上圖所示的 3D 對象操縱器,請確保視圖上方工具欄中的選擇模式處于活動狀態(tài)。

godot文檔,godot,3d,游戲

向下移動地面1米,以便有一個可見的編輯器網(wǎng)格。視口左下角的標簽告訴您節(jié)點的平移程度。

godot文檔,godot,3d,游戲

注:向下移動Ground節(jié)點會同時移動兩個子節(jié)點。確保移動Ground節(jié)點,不是MeshInstance3D或?CollisionShape3D。

最終,Ground的 transform.position.y 應該是 -1

godot文檔,godot,3d,游戲

讓我們添加一個定向光,這樣我們的場景就不會全是灰色的。選擇Main?節(jié)點并添加子節(jié)點DirectionalLight3D。

godot文檔,godot,3d,游戲

我們需要移動和旋轉(zhuǎn)DirectionalLight3D節(jié)點。通過單擊并拖動操縱器的綠色箭頭將其向上移動,然后單擊并拖動紅色圓弧使其繞 X 軸旋轉(zhuǎn),直到地面被點亮。

Inspector中,通過單擊復選框打開Shadow -> Enabled 。

godot文檔,godot,3d,游戲

此時,您的項目應該如下所示。

godot文檔,godot,3d,游戲

這是我們的【新起點】。在下一部分中,我們將處理玩家場景和基地運動。

玩家場景和輸入動作?

在接下來的兩節(jié)課中,我們將設(shè)計玩家場景、注冊自定義輸入操作以及編寫玩家移動代碼。到最后,您將擁有一個可以向八個方向移動的可玩角色。

通過轉(zhuǎn)到左上角的 Scene 菜單并單擊New Scene創(chuàng)建一個新場景。

godot文檔,godot,3d,游戲

創(chuàng)建一個CharacterBody3D節(jié)點作為根節(jié)點

godot文檔,godot,3d,游戲

將CharacterBody3D命名為Player。角色身體是對 2D 游戲教程中使用的區(qū)域和剛體的補充。像剛體一樣,它們可以移動并與環(huán)境發(fā)生碰撞,但不是由物理引擎控制,而是決定它們的運動。在編寫跳躍和擠壓機制代碼時,您將看到我們?nèi)绾问褂霉?jié)點的獨特功能。

參考:要了解有關(guān)不同物理節(jié)點類型的更多信息,請參閱?物理介紹。

現(xiàn)在,我們將為角色的 3D 模型創(chuàng)建一個基本裝備。這將允許我們稍后在播放動畫時通過代碼旋轉(zhuǎn)模型。

添加一個Node3D節(jié)點作為Player的子節(jié)點并命名為Pivot

godot文檔,godot,3d,游戲

然后,在文件系統(tǒng)??繖谥?,通過雙擊展開文件夾art/并將其下player.glb文件拖放到Pivot.

godot文檔,godot,3d,游戲

這應該將模型實例化為Pivot.?您可以將其重命名為Character.

godot文檔,godot,3d,游戲

注:這些.glb文件包含基于開源 GLTF 2.0 規(guī)范的 3D 場景數(shù)據(jù)。它們是專有格式(如 Godot 也支持的 FBX)的現(xiàn)代且強大的替代品。為了生成這些文件,我們在Blender 3D中設(shè)計了模型并將其導出到 GLTF。

與各種物理節(jié)點一樣,我們需要一個碰撞形狀讓我們的角色與環(huán)境發(fā)生碰撞。再次選擇該Player節(jié)點并添加一個子節(jié)點?CollisionShape3D。在Inspector的Shape屬性上,添加一個新的SphereShape3D。

godot文檔,godot,3d,游戲

球體的線框出現(xiàn)在角色下方。

godot文檔,godot,3d,游戲

這將是物理引擎用來與環(huán)境碰撞的形狀,所以我們希望它能更好地適應 3D 模型。通過在視口中拖動橙色點將其縮小一點。我的球體的半徑約為0.8米。

然后,向上移動形狀,使其底部與網(wǎng)格平面大致對齊。

godot文檔,godot,3d,游戲

您可以通過單擊PivotCharacter節(jié)點旁邊的眼睛圖標來切換模型的可見性?。

godot文檔,godot,3d,游戲

將場景另存為player.tscn

準備好節(jié)點后,我們幾乎可以開始編碼了。但首先,我們還需要定義一些輸入動作。

創(chuàng)建輸入操作?

要移動角色,我們將監(jiān)聽玩家的輸入,例如按箭頭鍵。在 Godot 中,雖然我們可以在代碼中編寫所有鍵綁定,但有一個強大的系統(tǒng)允許您為一組鍵和按鈕分配標簽。這簡化了我們的腳本并使它們更具可讀性。

該系統(tǒng)是輸入映射。要訪問其編輯器,請前往項目菜單并選擇項目設(shè)置。

godot文檔,godot,3d,游戲

在頂部,有多個選項卡。單擊輸入地圖【Input Map】。此窗口允許您在頂部添加新操作;他們是你的標簽。在底部,您可以將鍵綁定到這些操作。

godot文檔,godot,3d,游戲

Godot 項目帶有一些為用戶界面設(shè)計設(shè)計的預定義操作,我們可以在這里使用它們。但我們正在定義自己的游戲手柄。

我們將命名我們的動作move_left,?move_right,?move_forward,?move_back?和jump

要添加一個動作,請在頂部的欄中寫下它的名稱,然后按 Enter 鍵。

godot文檔,godot,3d,游戲

創(chuàng)建以下五個操作:

godot文檔,godot,3d,游戲

要將鍵或按鈕綁定到操作,請單擊其右側(cè)的“+”按鈕。這樣做是為了move_left。按向左箭頭鍵并單擊確定。

godot文檔,godot,3d,游戲

也將A鍵綁定到 move_left動作上。

godot文檔,godot,3d,游戲

現(xiàn)在讓我們添加對游戲手柄左操縱桿的支持。再次單擊“+”按鈕,但這次選擇Manual Selection -> Joypad Axes

godot文檔,godot,3d,游戲

選擇左操縱桿的負 X 軸。

godot文檔,godot,3d,游戲

將其他值保留為默認值,然后按OK

注:如果您希望控制器具有不同的輸入操作,您應該使用附加選項中的設(shè)備選項。設(shè)備 0 對應第一個插入的游戲手柄,設(shè)備 1 對應第二個插入的游戲手柄,依此類推。

對其他輸入操作執(zhí)行相同的操作。例如,將右箭頭 D 和左搖桿的正軸綁定到move_right。綁定所有鍵后,您的界面應如下所示。

godot文檔,godot,3d,游戲

最后要設(shè)置的動作是jump動作。綁定Space鍵和游戲手柄的A鍵。

godot文檔,godot,3d,游戲

您的跳轉(zhuǎn)輸入操作應如下所示。

godot文檔,godot,3d,游戲

這就是我們在這個游戲中需要的所有動作。您可以使用此菜單來標記項目中的任何按鍵和按鈕組。

在下一部分中,我們將編寫代碼并測試玩家的移動。

使用代碼移動Player?

是時候編碼了!我們將使用在上一部分中創(chuàng)建的輸入動作來移動角色。

右鍵單擊該Player節(jié)點并選擇附加腳本以向其添加新腳本。在彈出窗口中,在按下創(chuàng)建按鈕之前將模板設(shè)置為?。

godot文檔,godot,3d,游戲

讓我們從類的屬性開始。我們將定義一個移動速度,一個代表重力的下落加速度,以及一個我們將用來移動角色的速度。

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 矢量,它結(jié)合了速度和方向。在這里,我們將它定義為一個屬性,因為我們想要跨幀更新和重用它的值。

注:這些值與二維碼有很大不同,因為距離以米為單位。在 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()?虛函數(shù)進行所有計算。就像 一樣_process(),它允許您每幀更新節(jié)點,但它是專門為物理相關(guān)代碼設(shè)計的,例如移動運動學或剛體。

參考:_process()要了解有關(guān)和?之間區(qū)別的更多信息_physics_process(),請參閱空閑和物理處理。

我們首先將一個變量direction初始化為Vector3.ZERO.?然后,我們檢查玩家是否按下了一個或多個輸入move_*并相應地更新向量xz組件。這些對應于地平面的軸。

這四個條件給了我們八種可能,即八個可能的方向。

如果玩家同時按下 W 和 D,向量的長度約為1.4.?但是如果他們按下一個鍵,它的長度將是1.?我們希望向量的長度是一致的,而不是沿對角線移動得更快。為此,我們可以調(diào)用它的normalize()方法。

#func _physics_process(delta):
    #...

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

在這里,我們僅在方向的長度大于零時才對向量進行歸一化,這意味著玩家正在按下方向鍵。

在這種情況下,我們還獲取Pivot節(jié)點并調(diào)用其look_at()方法。此方法在空間中獲取一個位置,以在全局坐標和向上方向中查看。在這種情況下,我們可以使用Vector3.UP常量。

注:節(jié)點的局部坐標,如position,是相對于其父節(jié)點的。全局坐標,例如global_position,相對于您可以在視口中看到的世界主軸。

在 3D 中,包含節(jié)點位置的屬性是position。通過向其中添加direction,我們得到一個距離 Player1 米位置來查看。

然后,我們更新速度。我們必須分別計算地面速度和下落速度。【一定要注意縮進】以便這些行在_physics_process()函數(shù)內(nèi)部,但在我們上面剛剛寫的條件之外。

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

如果身體在此幀中與地板發(fā)生碰撞,則該CharacterBody3D.is_on_floor()函數(shù)返回true。這就是為什么我們只在Player在空中時才對其施加重力。

對于垂直速度,我們減去下降加速度乘以每幀的增量時間。這行代碼將導致我們的角色在每一幀中掉落,只要它沒有落在地板上或與地板發(fā)生碰撞。

如果發(fā)生移動和碰撞,物理引擎只能在給定幀期間檢測與墻壁、地板或其他物體的交互。稍后我們將使用此屬性來編寫跳轉(zhuǎn)代碼。

在最后一行,我們調(diào)用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()

測試我們玩家的動作?

我們將把我們的Player放在Main場景中進行測試。為此,我們需要實例化播放器,然后添加攝像機。與 2D 不同,在 3D 中,如果您的視口沒有相機指向某物,您將看不到任何東西。

保存Player場景并打開Main場景。您可以單擊?編輯器頂部的Main選項卡來執(zhí)行此操作。

godot文檔,godot,3d,游戲

如果您之前關(guān)閉了場景,請前往文件系統(tǒng)??繖诓㈦p擊?main.tscn以重新打開它。

要實例化Player,請右鍵單擊Main節(jié)點并選擇Instance Child Scene。

godot文檔,godot,3d,游戲

在彈出窗口中,雙擊player.tscn。角色應該出現(xiàn)在視口的中心。

添加相機?

接下來讓我們添加相機。就像我們對PlayerPivot所做的那樣,我們將創(chuàng)建一個基本的裝備。再次右鍵單擊該Main節(jié)點并選擇?添加子節(jié)點。創(chuàng)建一個新的Marker3D并命名CameraPivot。選擇并向其CameraPivot添加一個子節(jié)點Camera3D 。您的場景樹應如下所示。

godot文檔,godot,3d,游戲

當您選擇了相機時,請注意左上角出現(xiàn)的預覽復選框。您可以單擊它來預覽游戲中的相機投影。

godot文檔,godot,3d,游戲

我們將使用Pivot來旋轉(zhuǎn)相機,就像它在起重機上一樣。讓我們首先拆分 3D 視圖,以便能夠自由瀏覽場景并查看相機所見。

在視口正上方的工具欄中,點擊View,然后點擊2 Viewports。您也可以按Ctrl?+?2(在 macOS 上Cmd?+?2)。

godot文檔,godot,3d,游戲

godot文檔,godot,3d,游戲

在底部視圖中,選擇您的Camera3D并通過單擊復選框打開相機預覽。

godot文檔,godot,3d,游戲

在頂視圖中,沿Z 軸(藍色軸)向上移動相機19單位。

godot文檔,godot,3d,游戲

這就是魔法發(fā)生的地方。選擇CameraPivot并 繞 X 軸旋轉(zhuǎn)-45度(使用紅色圓圈)。您會看到相機像掛在起重機上一樣移動。

godot文檔,godot,3d,游戲

您可以通過按F6并按箭頭鍵移動角色來運行場景。

godot文檔,godot,3d,游戲

由于透視投影,我們可以在角色周圍看到一些空白空間。在此游戲中,我們將改用正交投影來更好地框定游戲區(qū)域并讓玩家更容易讀取距離。

再次選擇Camera并在Inspector中,將Projection設(shè)置為?Orthogonal并將Size設(shè)置為19。角色現(xiàn)在應該看起來更平坦,地面應該填滿背景。

注:在 Godot 4 中使用正交相機時,定向陰影質(zhì)量取決于相機的Far值。Far值越高,相機能夠看到的距離就越遠。但是,較高的Far值也會降低陰影質(zhì)量,因為陰影渲染必須覆蓋更大的距離。

如果在切換到正交相機后定向陰影看起來太模糊,請將相機的Far屬性減小到較低的值,例如?100.?不要將此Far屬性減小太多,否則遠處的對象將開始消失。

godot文檔,godot,3d,游戲

測試您的場景,您應該能夠在所有 8 個方向上移動,并且不會在地板上出現(xiàn)故障!

最終,我們同時擁有玩家移動和視野。接下來,我們將處理怪物。

設(shè)計MOB場景?

在這一部分中,您將為怪物編寫代碼,我們稱之為mob。在下一課中,我們將在可玩區(qū)域周圍隨機生成它們。

讓我們在新場景中設(shè)計怪物本身。節(jié)點結(jié)構(gòu)將與player.tscn場景相似。

再次創(chuàng)建一個以CharacterBody3D節(jié)點作為其根的場景。命名它?Mob。添加一個子節(jié)點Node3D,命名為Pivot。并將文件mob.glbFileSystem??繖谕戏诺?以Pivot將怪物的 3D 模型添加到場景中。

godot文檔,godot,3d,游戲

您可以將新創(chuàng)建??的mob節(jié)點重命名為Character.

godot文檔,godot,3d,游戲

我們需要一個碰撞形狀來讓我們的身體工作。右鍵單擊Mob場景的根節(jié)點,然后單擊添加子節(jié)點。

godot文檔,godot,3d,游戲

添加CollisionShape3D。

godot文檔,godot,3d,游戲

Inspector中,將BoxShape3D分配給Shape屬性。

godot文檔,godot,3d,游戲

我們應該改變它的大小以更好地適應 3D 模型。您可以通過單擊并拖動橙色點以交互方式執(zhí)行此操作。

盒子應該接觸地板并且比模型薄一點。物理引擎的工作方式是,如果玩家的球體接觸到盒子的角,就會發(fā)生碰撞。如果盒子比 3D 模型大了一點,你可能會死在離怪物很遠的地方,游戲會給玩家一種不公平的感覺。

godot文檔,godot,3d,游戲

請注意,我的盒子比怪物高。在這個游戲中沒問題,因為我們是從上方觀察場景并使用固定視角。碰撞形狀不必與模型完全匹配。當你測試它時,游戲的感覺應該決定它們的形式和大小。

移除屏幕外的怪物?

我們將在游戲關(guān)卡中定期生成怪物。如果我們不小心,它們的數(shù)量可能會增加到無窮大,而我們不希望這樣。每個生物實例都有內(nèi)存和處理成本,當生物在屏幕外時我們不想為此付費。

一旦一個怪物離開了屏幕,我們就不再需要它了,所以我們應該刪除它。Godot 有一個節(jié)點VisibleOnScreenNotifier3D可以檢測物體何時離開屏幕?,我們將使用它來摧毀我們的生物。

注:當您不斷實例化一個對象時,可以使用一種技術(shù)來避免一直創(chuàng)建和銷毀實例的成本,稱為池化。它包括預先創(chuàng)建一個對象數(shù)組并一遍又一遍地重復使用它們。

使用 GDScript 時,您無需擔心這一點。使用池的主要原因是避免使用垃圾收集語言(如 C# 或 Lua)凍結(jié)。GDScript 使用不同的技術(shù)來管理內(nèi)存,即引用計數(shù),它沒有那個警告。您可以在此處了解更多相關(guān)信息:內(nèi)存管理。

選擇該Mob節(jié)點并添加一個子節(jié)點VisibleOnScreenNotifier3D。出現(xiàn)另一個盒子,這次是粉紅色的。當這個盒子完全離開屏幕時,節(jié)點會發(fā)出一個信號。

godot文檔,godot,3d,游戲

使用橙色點調(diào)整它的大小,直到它覆蓋整個 3D 模型。

godot文檔,godot,3d,游戲

編碼MOB的運動?

讓我們來實現(xiàn)怪物的動作。我們將分兩步進行。首先,我們將在Mob上編寫一個腳本,定義一個初始化怪物的函數(shù)。然后我們將在main.tscn場景中編寫隨機生成機制的代碼并從那里調(diào)用函數(shù)。

將腳本附加到Mob.

godot文檔,godot,3d,游戲

這是開始的移動代碼。我們定義了兩個屬性min_speed?和max_speed來定義一個隨機速度范圍,稍后我們將使用它來定義CharacterBody3D.velocity。

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


func _physics_process(_delta):
    move_and_slide()

與玩家類似,我們通過調(diào)用函數(shù)CharacterBody3D.move_and_slide()在每一幀移動mob。這次,我們在每一幀不更新velocity;我們希望怪物以恒定的速度移動并離開屏幕,即使它會撞到障礙物。

我們需要定義另一個函數(shù)來計算CharacterBody3D.velocity.?此函數(shù)會將怪物轉(zhuǎn)向玩家并隨機化其運動角度和速度。

該函數(shù)將把怪物的生成位置start_position和?player_position作為它的參數(shù)。

我們將怪物定位在start_position并使用方法look_at_from_position()將其轉(zhuǎn)向玩家,并通過圍繞 Y 軸隨機旋轉(zhuǎn)一個角度來隨機化角度。下面代碼,randf_range()輸出一個介于弧度-PI/4和弧度PI/4之間的隨機值。

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

我們得到了一個隨機位置,現(xiàn)在我們需要一個random_speed.?函數(shù)randi_range()將很有用,因為它提供隨機 int 值,我們將使用min_speed與max_speed。?random_speed只是一個整數(shù),我們只是用它來乘以我們的CharacterBody3D.velocity.?應用random_speed后,我們將Vector3速度向量CharacterBody3D.velocity向玩家旋轉(zhuǎn)。

func initialize(start_position, player_position):
    # ...

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

離開屏幕?

我們?nèi)匀恍枰谏镫x開屏幕時消滅它們。為此,我們將VisibleOnScreenNotifier3D節(jié)點的screen_exited信號連接到Mob.

單擊編輯器頂部的3D標簽返回 3D 視口。您也可以按Ctrl?+?F2(在 macOS 上Alt?+?2)。

godot文檔,godot,3d,游戲

選擇VisibleOnScreenNotifier3D節(jié)點,然后在界面右側(cè)導航到節(jié)點停靠欄。雙擊screen_exited()信號。

godot文檔,godot,3d,游戲

將信號連接到Mob

godot文檔,godot,3d,游戲

這會將您帶回腳本編輯器并為您添加一個新函數(shù)?_on_visible_on_screen_notifier_3d_screen_exited().?在該函數(shù)中調(diào)用queue_free()?方法。這個函數(shù)銷毀它被調(diào)用的實例。

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

我們的怪物已經(jīng)準備好進入游戲了!在下一部分中,您將在游戲關(guān)卡中生成怪物。

這是完整的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

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

生成怪物?

在這一部分中,我們將沿著一條路徑隨機生成怪物。到最后,您將看到怪物在游戲板上漫游。

godot文檔,godot,3d,游戲

在文件系統(tǒng)中雙擊main.tscn打開Main場景。

在繪制路徑之前,我們要更改游戲分辨率。我們的游戲有一個默認的窗口大小1152x648。我們要把它設(shè)置為720x540,一個漂亮的小盒子。

轉(zhuǎn)到項目 -> 項目設(shè)置

godot文檔,godot,3d,游戲

在左側(cè)菜單中,向下導航至Display -> Window。在右側(cè),將?寬度設(shè)置為720,將高度設(shè)置為540

godot文檔,godot,3d,游戲

創(chuàng)建生成路徑?

就像您在 2D 游戲教程中所做的那樣,您將設(shè)計一條路徑并使用?PathFollow3D節(jié)點對其上的隨機位置進行采樣。

但是在 3D 中,繪制路徑有點復雜。我們希望它圍繞游戲視圖,這樣怪物就出現(xiàn)在屏幕外面。但是如果我們繪制一條路徑,我們將不會從相機預覽中看到它。

為了找到視圖的限制,我們可以使用一些占位符網(wǎng)格。您的視口仍應分為兩部分,相機預覽位于底部。如果不是這種情況,請按Ctrl?+?2(在 macOS 上Cmd?+?2) 將視圖一分為二。選擇Camera3D節(jié)點并單擊底部視口中的預覽復選框。

godot文檔,godot,3d,游戲

添加占位圓柱體?

讓我們添加占位符網(wǎng)格。添加一個新的Node3D作為?Main節(jié)點的子節(jié)點并將其命名為Cylinders。我們將使用它來對圓柱體進行分組。選擇Cylinders并添加子節(jié)點MeshInstance3D

godot文檔,godot,3d,游戲

Inspector中,將CylinderMesh分配給Mesh屬性。

godot文檔,godot,3d,游戲

使用視口左上角的菜單將頂部視口設(shè)置為頂部正交視圖?;蛘?,您可以按鍵盤上的 7 鍵。

godot文檔,godot,3d,游戲

網(wǎng)格可能會分散注意力。您可以通過轉(zhuǎn)到?工具欄中的“查看”菜單并單擊“查看網(wǎng)格”來切換它。

godot文檔,godot,3d,游戲

您現(xiàn)在想要沿著地平面移動圓柱體,在底部視口中查看相機預覽。我建議使用網(wǎng)格捕捉來這樣做。您可以通過單擊工具欄中的磁鐵圖標或按 Y 來切換它。

godot文檔,godot,3d,游戲

移動圓柱體,使其位于左上角相機視野之外。

godot文檔,godot,3d,游戲

我們將創(chuàng)建網(wǎng)格的副本并將它們放置在游戲區(qū)域周圍。按Ctrl?+?D(在 macOS 上Cmd?+?D) 復制節(jié)點。您還可以右鍵單擊場景停靠欄中的節(jié)點,然后選擇復制。沿藍色 Z 軸向下移動副本,直到它正好位于相機預覽之外。

通過按下Shift鍵并單擊未選擇的圓柱體并復制它們來選擇兩個圓柱體。

godot文檔,godot,3d,游戲

通過拖動紅色 X 軸將它們向右移動。

godot文檔,godot,3d,游戲

白色有點難看,不是嗎?讓我們通過給他們一種新材料讓他們脫穎而出。

在 3D 中,材質(zhì)定義了表面的視覺屬性,例如顏色、反射光的方式等。我們可以使用它們來改變網(wǎng)格的顏色。

我們可以一次更新所有四個圓柱體。選擇場景??繖谥械乃芯W(wǎng)格實例?。為此,您可以單擊第一個,然后按住 Shift 單擊最后一個。

godot文檔,godot,3d,游戲

Inspector中,展開Material部分并將StandardMaterial3D分配給 slot?0。

godot文檔,godot,3d,游戲

godot文檔,godot,3d,游戲

單擊球體圖標以打開材料資源。您可以預覽材料和一長串填充有屬性的部分。您可以使用它們來創(chuàng)建各種表面,從金屬到巖石或水。

展開反照率部分。

godot文檔,godot,3d,游戲

將顏色設(shè)置為與背景形成對比的顏色,例如亮橙色。

godot文檔,godot,3d,游戲

我們現(xiàn)在可以使用圓柱體作為指南。單擊它們旁邊的灰色箭頭,將它們折疊到場景停靠欄中。展望未來,您還可以通過單擊Cylinders旁邊的眼睛圖標來切換它們的可見性。

godot文檔,godot,3d,游戲

添加子節(jié)點Path3D到Main節(jié)點。在工具欄中,出現(xiàn)四個圖標。單擊添加點工具,即帶有綠色“+”符號的圖標。

godot文檔,godot,3d,游戲

您可以將鼠標懸停在任何圖標上以查看描述該工具的工具提示。

單擊每個圓柱體的中心以創(chuàng)建一個點。然后,單擊工具欄中的關(guān)閉曲線圖標以關(guān)閉路徑。如果任何點有點偏離,您可以單擊并拖動它以重新定位。

godot文檔,godot,3d,游戲

你的路徑應該是這樣的。

godot文檔,godot,3d,游戲

要對其上的隨機位置進行采樣,我們需要一個PathFollow3D節(jié)點。添加一個?PathFollow3D作為Path3D.?分別將這兩個節(jié)點重命名為SpawnPath和?SpawnLocation。它更能描述我們將使用它們做什么。

godot文檔,godot,3d,游戲

這樣,我們就可以編寫生成機制的代碼了。

隨機生成怪物?

右鍵單擊該Main節(jié)點并將新腳本附加到它。

我們首先將一個變量導出到Inspector,以便我們可以mob.tscn?為其分配或任何其他怪物。

extends Node

@export var mob_scene: PackedScene

我們希望以固定的時間間隔生成生物。為此,我們需要回到場景并添加一個計時器。不過,在此之前,我們需要將?mob.tscn文件分配給mob_scene上面的屬性(否則它為空?。?/p>

返回 3D 屏幕并選擇Main節(jié)點。從FileSystem停靠欄拖動mob.tscnInspector中的Mob Scene插槽。

godot文檔,godot,3d,游戲

添加一個新的Timer節(jié)點作為 的子節(jié)點Main。命名它MobTimer。

godot文檔,godot,3d,游戲

Inspector中,將其Wait Time設(shè)置為0.5秒并打開?Autostart以便它在我們運行游戲時自動啟動。

godot文檔,godot,3d,游戲

定時器timeout每次到達等待時間結(jié)束時都會發(fā)出一個信號。默認情況下,它們會自動重啟,并循環(huán)發(fā)出信號。我們可以從主節(jié)點連接到這個信號,每0.5秒生成一次怪物。

MobTimer仍處于選中狀態(tài)的情況下,前往右側(cè)的節(jié)點??繖?,然后雙擊信號timeout。

godot文檔,godot,3d,游戲

將其連接到節(jié)點。

godot文檔,godot,3d,游戲

這會將您帶回腳本,并帶有一個新的空?_on_mob_timer_timeout()函數(shù)。

讓我們編寫MOB生成邏輯。我們要:

  1. 實例化MOB場景。

  2. 在生成路徑上的隨機位置采樣。

  3. 獲取玩家的位置。

  4. 調(diào)用生物的initialize()方法,將隨機位置和玩家的位置傳遞給它。

  5. 將生物添加為主節(jié)點的子節(jié)點。

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)

上面,randf()在0和1之間產(chǎn)生一個隨機值,這是PathFollow節(jié)點所期望的:0 是路徑的起點,1 是路徑的終點。我們設(shè)置的路徑圍繞相機的視口,因此 0 到 1 之間的任何隨機值progress_ratio都是視口邊緣的隨機位置!

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)

您可以按F6測試場景。你應該看到怪物生成并直線移動。

godot文檔,godot,3d,游戲

現(xiàn)在,當它們的路徑交叉時,它們會相互碰撞和滑動。我們將在下一部分解決這個問題。

跳躍和擠壓怪物?

在這一部分中,我們將添加跳躍和擠壓怪物的能力。在下一課中,我們會讓玩家在怪物擊中地面時死亡。

首先,我們必須更改一些與物理交互相關(guān)的設(shè)置。進入物理層的世界。

控制物理相互作用?

物理實體可以訪問兩個互補屬性:圖層【layers】和遮罩【mask】。圖層定義對象位于哪個物理層上。

遮罩控制身體將聆聽和檢測的層次。這會影響碰撞檢測。當你想讓兩個物體相互作用時,你至少需要一個物體有一個與另一個物體相對應的掩碼。

如果這讓您感到困惑,請不要擔心,我們馬上會看到三個示例。

重要的一點是您可以使用圖層和遮罩來過濾物理交互、控制性能并消除代碼中對額外條件的需求。

默認情況下,所有物理體和區(qū)域 layer 和 mask都設(shè)置為1。這意味著它們都相互碰撞。

物理層由數(shù)字表示,但我們可以給它們命名以跟蹤什么是什么。

設(shè)置圖層名稱?

讓我們給物理層起個名字。轉(zhuǎn)到項目 -> 項目設(shè)置。

godot文檔,godot,3d,游戲

在左側(cè)菜單中,向下導航至Layer Names -> 3D Physics。您可以在右側(cè)看到一個圖層列表,每個圖層旁邊都有一個字段。你可以在那里設(shè)置他們的名字。分別命名前三層player、enemyworld

godot文檔,godot,3d,游戲

現(xiàn)在,我們可以將它們分配給我們的物理節(jié)點。

分配圖層和蒙版?

場景中,選擇Ground節(jié)點。在Inspector中,展開?Collision部分。在那里,您可以將節(jié)點的圖層和蒙版視為按鈕網(wǎng)格。

godot文檔,godot,3d,游戲

地面是世界的一部分,所以我們希望它成為第三層的一部分。單擊亮起的按鈕關(guān)閉第一打開第三層。然后,通過單擊關(guān)閉版。

godot文檔,godot,3d,游戲

如前所述,Mask屬性允許節(jié)點監(jiān)聽與其他物理對象的交互,但我們不需要它發(fā)生碰撞。Ground不需要聽任何東西;它只是為了防止生物掉落。

請注意,您可以單擊屬性右側(cè)的“...”按鈕來查看已命名復選框的列表。

godot文檔,godot,3d,游戲

接下來是PlayerMob。通過雙擊文件系統(tǒng)??繖谥械奈募?code>player.tscn打開。

選擇Player節(jié)點并將其Collision -> Mask設(shè)置為“enemies”和“world”。您可以保留默認Layer屬性不變,因為第一層是“播放器”層。

godot文檔,godot,3d,游戲

然后,雙擊打開Mobmob.tscn場景并選擇?Mob節(jié)點。

將其Collision -> Layer設(shè)置為“enemies”并取消其Collision -> Mask 的設(shè)置,使遮罩為空。

godot文檔,godot,3d,游戲

這些設(shè)置意味著怪物將相互移動。如果你想讓怪物相互碰撞和滑動,打開敵人”面具。

注:生物不需要屏蔽“世界”層,因為它們只在 XZ 平面上移動。我們不會通過設(shè)計對它們施加任何重力。

跳躍?

跳躍機制本身只需要兩行代碼。打開播放器?腳本。我們需要一個值來控制跳躍的強度并更新?_physics_process()以對跳躍進行編碼。

在定義fall_acceleration的行之后,在腳本的頂部,添加jump_impulse.

#...
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20

在內(nèi)部_physics_process(),在代碼塊之前添加以下代碼move_and_slide()。

func _physics_process(delta):
    #...

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

    #...

這就是你需要跳的全部!

is_on_floor()方法是來自該類的工具CharacterBody3D。true如果身體在此幀中與地板碰撞,它會返回。這就是為什么我們對Player施加重力:所以我們與地板碰撞而不是像怪物那樣漂浮在地板上。

如果角色在地板上并且玩家按下“跳躍”,我們會立即給他們很大的垂直速度。在游戲中,您真的希望控件能夠響應并提供像這樣的即時速度提升,雖然不切實際,但感覺很棒。

請注意,Y 軸向上為正。這與 2D 不同,其中 Y 軸向下為正。

壓扁怪物?

接下來讓我們添加壁球機制。我們要讓角色在怪物身上彈跳并同時殺死它們。

我們需要檢測與怪物的碰撞并將它們與與地板的碰撞區(qū)分開來。為此,我們可以使用 Godot 的組標記功能。

再次打開場景mob.tscn并選擇Mob節(jié)點。轉(zhuǎn)到?右側(cè)的節(jié)點??繖谝圆榭葱盘柫斜?em>。Node?dock 有兩個選項卡:?您已經(jīng)使用過的Signals和允許您將標簽分配給節(jié)點的Groups?

單擊它以顯示一個字段,您可以在其中寫入標簽名稱。在字段中輸入“mob”,然后單擊“添加”按鈕。

godot文檔,godot,3d,游戲

場景??繖谥袝霈F(xiàn)一個圖標,指示該節(jié)點至少屬于一個組。

godot文檔,godot,3d,游戲

我們現(xiàn)在可以使用代碼中的組來區(qū)分與怪物的碰撞和與地板的碰撞。

編碼擠壓機制?

回到Player腳本來編寫擠壓和彈跳的代碼。

在腳本的頂部,我們需要另一個屬性bounce_impulse.?壓扁敵人時,我們不一定希望角色像跳躍時那樣飛得那么高。

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 16

然后,在我們在上面_physics_process()添加的Jumping代碼塊之后,添加以下循環(huán)。使用 時?move_and_slide(),Godot 有時會連續(xù)多次移動身體以平滑角色的運動。所以我們必須遍歷所有可能發(fā)生的碰撞。

在循環(huán)的每次迭代中,我們檢查我們是否降落在暴徒身上。如果是這樣,我們殺死它并反彈。

使用此代碼,如果給定幀上沒有發(fā)生碰撞,則循環(huán)不會運行。

func _physics_process(delta):
   #...

   # Iterate through all collisions that occurred this frame
   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

這是很多新功能。這里有一些關(guān)于它們的更多信息。

函數(shù)get_slide_collision_count()get_slide_collision()都來自CharacterBody3D類,并且與?move_and_slide()相關(guān).

get_slide_collision()返回一個?KinematicCollision3D對象,該對象包含有關(guān)碰撞發(fā)生的位置和方式的信息。例如,我們使用它的get_collider屬性,通過調(diào)用is_in_group()來檢查我們是否與“暴徒”發(fā)生碰撞 :?collision.get_collider().is_in_group("mob")

注:該is_in_group()方法在每個Node上都可用。

為了檢查我們是否降落在怪物身上,我們使用向量點積:Vector3.UP.dot(collision.get_normal())?>?0.1。碰撞法線是垂直于發(fā)生碰撞的平面的 3D 矢量。點積允許我們將其與向上方向進行比較。

對于點積,當結(jié)果大于 時0,兩個向量的夾角小于 90 度。高于的值0.1告訴我們,我們大致在怪物之上。

我們正在調(diào)用一個未定義的函數(shù)mob.squash(),因此我們必須將它添加到 Mob 類中。

通過在文件系統(tǒng)??繖谥须p擊Mob.gd腳本來打開它。在腳本的頂部,我們要定義一個名為squashed的新信號。在底部,您可以添加 squash 函數(shù),我們可以在其中發(fā)出信號并摧毀生物。

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    squashed.emit()
    queue_free()

我們將在下一課中使用該信號為得分加分。

有了它,你應該能夠通過跳上它們來殺死怪物。您可以按?F5嘗試游戲并設(shè)置main.tscn為項目的主場景。

但是,玩家還不會死。我們將在下一部分進行處理。

殺死玩家?

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

我們想要檢測被敵人擊中與壓扁他們的方式不同。我們希望玩家在地板上移動時死亡,但在空中則不會。我們可以使用矢量數(shù)學來區(qū)分這兩種碰撞。不過,我們將使用Area3D節(jié)點,它適用于碰撞盒。

帶有 Area 節(jié)點的 Hitbox?

回到player.tscn場景并添加一個新的子節(jié)點Area3D。將其命名?MobDetector?為添加一個CollisionShape3D節(jié)點作為它的子節(jié)點。

godot文檔,godot,3d,游戲

Inspector中,為其指定一個圓柱體形狀。

godot文檔,godot,3d,游戲

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

godot文檔,godot,3d,游戲

您還希望圓柱體比球體寬。這樣,玩家在碰撞并被推到怪物的碰撞箱頂部之前就被擊中了。

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

接下來,再次選擇該MobDetector節(jié)點,并在Inspector中關(guān)閉其Monitorable屬性。這使得其他物理節(jié)點無法檢測到該區(qū)域。互補的Monitoring屬性允許它檢測碰撞。然后,移除Collision -> Layer并將蒙版設(shè)置為“enemies”層。

godot文檔,godot,3d,游戲

當區(qū)域檢測到碰撞時,它們會發(fā)出信號。我們將把一個連接到Player節(jié)點。選擇MobDetector并轉(zhuǎn)到Inspector節(jié)點選項卡,雙擊?body_entered信號并將其連接到Player

godot文檔,godot,3d,游戲

MobDetector將在CharacterBody3D或?RigidBody3D節(jié)點進入發(fā)射body_entered。因為它只屏蔽“敵人”物理層,所以它只會檢測Mob節(jié)點。???????

在代碼方面,我們要做兩件事:發(fā)出一個信號,稍后我們將使用它來結(jié)束游戲并摧毀玩家。我們可以將這些操作包裝在一個die()函數(shù)中,幫助我們在代碼上貼上描述性標簽。

# 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。如果一切設(shè)置正確,角色應該在敵人撞上碰撞器時死亡。請注意,沒有Player,以下行

var player_position = $Player.position

給出錯誤,因為沒有 $Player!

另請注意,與玩家碰撞并死亡的敵人取決于?Player和 的Mob碰撞形狀的大小和位置。您可能需要移動它們并調(diào)整它們的大小以獲得緊湊的游戲感覺。

結(jié)束游戲?

我們可以使用Playerhit信號來結(jié)束游戲。我們需要做的就是將它連接到Main節(jié)點并停止MobTimer反應。

打開main.tscn,選擇Player節(jié)點,然后在Node?dock 中,將其hit信號連接到Main節(jié)點。

godot文檔,godot,3d,游戲

在函數(shù)中獲取計時器并停止它_on_player_hit()。

func _on_player_hit():
    $MobTimer.stop()

如果你現(xiàn)在嘗試游戲,怪物會在你死后停止生成,剩下的會離開屏幕。

您可以表揚一下自己:您制作了一個完整的 3D 游戲原型,即使它還有些粗糙。

從那里,我們將添加一個分數(shù),重試游戲的選項,您將看到如何使用簡約的動畫讓游戲感覺更加生動。

代碼檢查點?

Main以下是、MobPlayer節(jié)點的完整腳本,供參考。您可以使用它們來比較和檢查您的代碼。

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

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

下一節(jié)課見,添加分數(shù)和重玩選項。

得分和重玩?

在這一部分,我們將添加得分、音樂播放和重新啟動游戲的功能。

我們必須在變量中跟蹤當前分數(shù),并使用最小界面將其顯示在屏幕上。我們將使用文本標簽來做到這一點。

在主場景中,添加Main一個新的子節(jié)點Control并命名為UserInterface。您將自動進入 2D 屏幕,您可以在其中編輯用戶界面 (UI)。

添加Label節(jié)點并命名ScoreLabel

godot文檔,godot,3d,游戲

Inspector中,將Label文本設(shè)置為占位符,如“Score: 0”。

godot文檔,godot,3d,游戲

此外,文本默認為白色,就像我們游戲的背景一樣。我們需要改變它的顏色才能在運行時看到它。

向下滾動到Theme Overrides,展開Colors?并啟用Font Color以便將文本著色為黑色(與白色 3D 場景形成鮮明對比)

godot文檔,godot,3d,游戲

最后,單擊并拖動視口中的文本,將其從左上角移開。

godot文檔,godot,3d,游戲

UserInterface節(jié)點允許我們將 UI 分組到場景樹的一個分支中,并使用將傳播到其所有子節(jié)點的主題資源。我們將使用它來設(shè)置我們游戲的字體。

創(chuàng)建 UI 主題?

再次選擇UserInterface節(jié)點。在Inspector中,在Theme -> Theme中創(chuàng)建一個新的主題資源。

godot文檔,godot,3d,游戲

單擊它以在底部面板中打開主題編輯器。它使您可以預覽所有內(nèi)置 UI 小部件與您的主題資源的外觀。

godot文檔,godot,3d,游戲

默認情況下,一個主題只有一個屬性,即Default Font。

參閱:您可以向主題資源添加更多屬性以設(shè)計復雜的用戶界面,但這超出了本系列的范圍。要了解有關(guān)創(chuàng)建和編輯主題的更多信息,請參閱GUI 皮膚簡介。

這需要一個字體文件,就像您計算機上的字體文件一樣。兩種常見的字體文件格式是 TrueType 字體 (TTF) 和 OpenType 字體 (OTF)。

FileSystem?dock 中,展開fonts目錄并單擊我們包含在項目中的文件Montserrat-Medium.ttf并將其拖到?Default Font上。文本將重新出現(xiàn)在主題預覽中。

文字有點小。將默認字體大小設(shè)置為22像素以增加文本的大小。

godot文檔,godot,3d,游戲

跟蹤分數(shù)?

接下來讓我們研究分數(shù)。將新腳本附加到ScoreLabel并定義score變量。

extends Label

var score = 0

每次我們壓扁一個怪物時,分數(shù)應該增加1。我們可以使用他們的squashed信號來知道什么時候發(fā)生。但是,因為我們從代碼中實例化了怪物,所以我們通過編輯器無法將 mob 信號連接到ScoreLabel。

相反,我們必須在每次生成怪物時從代碼中建立連接。

打開腳本main.gd。如果它仍然打開,您可以在腳本編輯器的左欄中單擊它的名稱。

godot文檔,godot,3d,游戲

或者,您可以雙擊文件系統(tǒng)main.gd??繖谥械奈募?。

在函數(shù)的底部_on_mob_timer_timeout(),添加以下行:

func _on_mob_timer_timeout():
    #...
    # We connect the mob to the score label to update the score upon squashing one.
    mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())

這一行的意思是,當生物發(fā)出信號時squashed,?ScoreLabel節(jié)點將接收信號并調(diào)用函數(shù)_on_mob_squashed()

返回ScoreLabel.gd腳本以定義_on_mob_squashed()?回調(diào)函數(shù)。

在那里,我們增加分數(shù)并更新顯示的文本。

func _on_mob_squashed():
    score += 1
    text = "Score: %s" % score

第二行使用變量的值score來代替占位符%s。使用該特性時,Godot 會自動將值轉(zhuǎn)換為字符串文本,方便在標簽中輸出文本或使用函數(shù)時使用print()。

參閱:您可以在此處了解有關(guān)字符串格式化的更多信息:GDScript 格式化字符串。在 C# 中,考慮使用帶有 "$" 的字符串插值。

您現(xiàn)在可以玩游戲并壓扁一些敵人以查看得分增加。

godot文檔,godot,3d,游戲

注:在復雜的游戲中,您可能希望將用戶界面與游戲世界完全分開。在那種情況下,您不會跟蹤標簽上的分數(shù)。相反,您可能希望將其存儲在一個單獨的專用對象中。但是當制作原型或當你的項目很簡單時,讓你的代碼保持簡單是很好的。編程始終是一種平衡行為。

重玩游戲?

我們現(xiàn)在將添加死后再次播放的功能。當玩家死亡時,我們將在屏幕上顯示一條消息并等待輸入。

回到main.tscn場景,選擇UserInterface節(jié)點,添加一個子節(jié)點ColorRect,并將其命名為Retry。該節(jié)點用統(tǒng)一的顏色填充一個矩形,并將用作使屏幕變暗的覆蓋層。

要使其跨越整個視口,您可以使用工具欄中的“錨點預設(shè)”菜單。

godot文檔,godot,3d,游戲

打開它并應用Full Rect命令。

godot文檔,godot,3d,游戲

什么都沒發(fā)生。好吧,幾乎沒有;只有四個綠色圖釘移動到選擇框的角落。

godot文檔,godot,3d,游戲

這是因為 UI 節(jié)點(所有帶有綠色圖標的節(jié)點)使用相對于其父邊界框的錨點和邊距。在這里,UserInterface節(jié)點的尺寸很小,并且Retry受其限制。

選擇UserInterface并應用Anchor Preset -> Full Rect。該?Retry節(jié)點現(xiàn)在應該跨越整個視口。

讓我們改變它的顏色,使游戲區(qū)域變暗。選擇Retry并在?Inspector中,將其Color設(shè)置為深色和透明的顏色。為此,在顏色選擇器中,將A滑塊拖動到左側(cè)。它控制顏色的 Alpha 通道,也就是說,它的不透明度/透明度。

godot文檔,godot,3d,游戲

接下來,添加一個Label作為子項Retry,并為其提供文本?“Press Enter to retry”。要移動它并將其錨定在屏幕中央,請?對其應用錨定預設(shè) -> 中心。

godot文檔,godot,3d,游戲

編碼重玩選項?

我們現(xiàn)在可以使用代碼來在Retry玩家死亡并再次玩游戲時顯示和隱藏節(jié)點。

打開腳本main.gd。首先,我們想在游戲開始時隱藏疊加層。將此行添加到_ready()函數(shù)中。

func _ready():
    $UserInterface/Retry.hide()

然后,當玩家被擊中時,我們會顯示疊加層。

func _on_player_hit():
    #...
    $UserInterface/Retry.show()

最后,當Retry節(jié)點可見時,我們需要監(jiān)聽玩家的輸入,如果他們按下 enter 則重新啟動游戲。為此,我們使用內(nèi)置?_unhandled_input()回調(diào),它在任何輸入時都會觸發(fā)。

如果玩家按下預定義的ui_accept輸入操作并且Retry可見,我們將重新加載當前場景。

func _unhandled_input(event):
    if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
        # This restarts the current scene.
        get_tree().reload_current_scene()

該函數(shù)get_tree()使我們能夠訪問全局SceneTree對象,這使我們能夠重新加載和重新啟動當前場景。

添加音樂?

要添加在后臺連續(xù)播放的音樂,我們將使用 Godot 中的另一個功能:自動加載。

要播放音頻,您需要做的就是將一個AudioStreamPlayer節(jié)點添加到您的場景并向其附加一個音頻文件。當您啟動場景時,它可以自動播放。但是,當您重新加載場景時,就像我們再次播放一樣,音頻節(jié)點也會重置,音樂會從頭開始播放。

您可以使用自動加載功能讓 Godot 在游戲開始時自動加載當前場景之外的節(jié)點或場景。您還可以使用它來創(chuàng)建全局可訪問的對象。

通過轉(zhuǎn)到“場景”菜單并單擊“新建場景”?或使用當前打開的場景旁邊的+圖標來創(chuàng)建新場景。

godot文檔,godot,3d,游戲

單擊Other Node按鈕創(chuàng)建一個AudioStreamPlayer并將其重命名為?MusicPlayer.

godot文檔,godot,3d,游戲

art/我們在目錄中包含了音樂配樂House In a Forest Loop.ogg,單擊并將其拖到Inspector中的Stream屬性上。此外,打開自動播放,以便在游戲開始時自動播放音樂。

godot文檔,godot,3d,游戲

將場景另存為MusicPlayer.tscn.

我們必須將其注冊為自動加載。前往Project -> Project Settings…菜單并單擊Autoload選項卡。

路徑字段中,您要輸入場景的路徑。單擊文件夾圖標打開文件瀏覽器并雙擊MusicPlayer.tscn.?然后,點擊右側(cè)的添加按鈕注冊節(jié)點。

godot文檔,godot,3d,游戲

MusicPlayer.tscn現(xiàn)在加載到您打開或播放的任何場景中。所以如果你現(xiàn)在運行游戲,音樂會在任何場景中自動播放。

在我們結(jié)束本課之前,讓我們快速了解一下它是如何工作的。當您運行游戲時,您的Scene?dock 會更改為您提供兩個選項卡:?RemoteLocal。

godot文檔,godot,3d,游戲

遠程選項卡允許您可視化正在運行的游戲的節(jié)點樹。在那里,您將看到節(jié)點和場景包含的所有內(nèi)容以及底部的實例化生物。

godot文檔,godot,3d,游戲

頂部是自動加載節(jié)點MusicPlayer根節(jié)點,這是您游戲的視口。

這就是本課的內(nèi)容。在下一部分中,我們將添加一個動畫,使游戲的外觀和感覺都更好。

這是完整的main.gd腳本以供參考。

extends Node

@export var mob_scene: PackedScene

func _ready():
    $UserInterface/Retry.hide()


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)

    # We connect the mob to the score label to update the score upon squashing one.
    mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())

func _on_player_hit():
    $MobTimer.stop()
    $UserInterface/Retry.show()

func _unhandled_input(event):
    if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
        # This restarts the current scene.
        get_tree().reload_current_scene()

角色動畫?

在最后一課中,我們將使用 Godot 的內(nèi)置動畫工具讓我們的角色漂浮和拍打。您將學習在編輯器中設(shè)計動畫并使用代碼讓您的游戲充滿活力。

godot文檔,godot,3d,游戲

我們將從介紹使用動畫編輯器開始。

使用動畫編輯器?

該引擎帶有在編輯器中創(chuàng)作動畫的工具。然后您可以使用代碼在運行時播放和控制它們。

打開播放器場景,選擇Player節(jié)點,并添加一個AnimationPlayer節(jié)點。

動畫???/em>欄出現(xiàn)在底部面板中。

godot文檔,godot,3d,游戲

它的頂部有一個工具欄和動畫下拉菜單,中間有一個當前為空的軌道編輯器,底部有過濾、捕捉和縮放選項。

讓我們創(chuàng)建一個動畫。單擊動畫 -> 新建。

godot文檔,godot,3d,游戲

將動畫命名為“浮動”。

godot文檔,godot,3d,游戲

一旦您創(chuàng)建了動畫,時間線就會出現(xiàn),其中的數(shù)字代表時間(以秒為單位)。

godot文檔,godot,3d,游戲

我們希望動畫在游戲開始時自動開始播放。此外,它應該循環(huán)。

為此,您可以分別單擊動畫工具欄中帶有“A+”圖標的按鈕和循環(huán)箭頭。

godot文檔,godot,3d,游戲

您還可以通過單擊右上角的固定圖標來固定動畫編輯器。這可以防止它在您單擊視口并取消選擇節(jié)點時折疊。

godot文檔,godot,3d,游戲

在??繖诘挠疑辖菍赢嫵掷m(xù)時間設(shè)置為1.2秒。

godot文檔,godot,3d,游戲

你應該看到灰色絲帶變寬了一點。它向您顯示動畫的開始和結(jié)束,垂直的藍線是您的時間光標。

godot文檔,godot,3d,游戲

您可以單擊并拖動右下角的滑塊來放大和縮小時間線。

godot文檔,godot,3d,游戲

浮動動畫?

使用動畫播放器節(jié)點,您可以根據(jù)需要為任意數(shù)量的節(jié)點上的大多數(shù)屬性設(shè)置動畫。請注意Inspector中屬性旁邊的鑰匙圖標。您可以單擊其中任何一個來為相應的屬性創(chuàng)建關(guān)鍵幀、時間和值對。關(guān)鍵幀將插入時間軸中時間光標所在的位置。

讓我們插入我們的第一把鑰匙。在這里,我們將為節(jié)點的位置和旋轉(zhuǎn)設(shè)置動畫Character。

選擇Character并在Inspector中展開Transform部分。單擊PositionRotation旁邊的鑰匙圖標。

godot文檔,godot,3d,游戲

godot文檔,godot,3d,游戲

對于本教程,只需創(chuàng)建默認選擇的 RESET Track(s)

兩個軌道出現(xiàn)在編輯器中,每個關(guān)鍵幀都有一個菱形圖標。

godot文檔,godot,3d,游戲

您可以單擊并拖動菱形以及時移動它們。將位置鍵移至0.2秒,將旋轉(zhuǎn)鍵移至0.1秒。

godot文檔,godot,3d,游戲

通過在灰色時間線上單擊并拖動,將時間光標移動到0.5秒。

godot文檔,godot,3d,游戲

Inspector中,將PositionY軸設(shè)置為0.65米,將RotationX軸設(shè)置為8.

godot文檔,godot,3d,游戲

為兩個屬性創(chuàng)建一個關(guān)鍵幀

godot文檔,godot,3d,游戲

0.7?現(xiàn)在,通過在時間軸上拖動將位置關(guān)鍵幀移動到秒。

godot文檔,godot,3d,游戲

注:關(guān)于動畫原理的講座超出了本教程的范圍。請注意,您不想均勻地安排時間和空間。相反,動畫師使用時間和間隔這兩個核心動畫原則。您想抵消和對比角色的動作,讓他們感覺自己還活著。

將時間光標移動到動畫的末尾,以1.2秒為單位。將 Y 位置設(shè)置為大約0.35,將 X 旋轉(zhuǎn)設(shè)置為-9度數(shù)。再次為這兩個屬性創(chuàng)建一個鍵。

godot文檔,godot,3d,游戲

您可以通過單擊播放按鈕或按Shift?+?D來預覽結(jié)果。單擊停止按鈕S或按停止播放。

godot文檔,godot,3d,游戲

您可以看到引擎在關(guān)鍵幀之間進行插值以生成連續(xù)的動畫。不過目前,這個動作感覺非常機械化。這是因為默認插值是線性的,導致不斷的過渡,這與現(xiàn)實世界中生物的移動方式不同。

我們可以使用緩動曲線來控制關(guān)鍵幀之間的過渡。

單擊并拖動時間軸中的前兩個鍵以框選它們。

godot文檔,godot,3d,游戲

您可以在Inspector中同時編輯兩個鍵的屬性,您可以在其中看到一個Easing屬性。

godot文檔,godot,3d,游戲

單擊并拖動曲線,將其向左拉。這將使它緩出,也就是說,最初過渡很快,隨著時間光標到達下一個關(guān)鍵幀而減慢。

godot文檔,godot,3d,游戲

再次播放動畫以查看不同之處。上半場應該已經(jīng)感覺有點活潑了。

對旋轉(zhuǎn)軌道中的第二個關(guān)鍵幀應用緩出。

godot文檔,godot,3d,游戲

對第二個位置關(guān)鍵幀執(zhí)行相反的操作,將其拖到右側(cè)。

godot文檔,godot,3d,游戲

你的動畫應該看起來像這樣。

godot文檔,godot,3d,游戲

注:動畫每幀更新動畫節(jié)點的屬性,覆蓋初始值。如果我們直接為Player節(jié)點設(shè)置動畫,它會阻止我們在代碼中移動它。這就是Pivot節(jié)點派上用場的地方:即使我們?yōu)?em>Character設(shè)置了動畫,我們?nèi)匀豢梢栽谀_本中移動和旋轉(zhuǎn)Pivot以及在動畫頂部更改圖層。

如果您玩游戲,玩家的生物現(xiàn)在會漂?。?/p>

如果該生物離地面有點太近,您可以Pivot向上移動以抵消它。

在代碼中控制動畫?

我們可以使用代碼根據(jù)玩家的輸入來控制動畫播放。讓我們改變角色移動時的動畫速度。

通過單擊旁邊的腳本圖標打開Player的腳本。

godot文檔,godot,3d,游戲

在 中_physics_process(),在我們檢查向量的行之后direction?,添加以下代碼。

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...
        $AnimationPlayer.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 1

這段代碼使得當玩家移動時,我們將播放速度乘以?4。當他們停止時,我們將其重置為正常。

我們提到Pivotcould 層在動畫之上進行變換。我們可以使用以下代碼行在跳躍時制作角色弧線。在末尾添加它_physics_process()。

func _physics_process(delta):
    #...
    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse

動畫生物?

這是 Godot 中動畫的另一個不錯的技巧:只要使用類似的節(jié)點結(jié)構(gòu),就可以將它們復制到不同的場景中。

例如,場景MobPlayer場景都有Pivot一個?Character節(jié)點,所以我們可以在它們之間重用動畫。

打開Player場景,選擇 AnimationPlayer 節(jié)點并打開“浮動”動畫。接下來,單擊“動畫”>“復制”。然后打開mob.tscn,創(chuàng)建一個AnimationPlayer子節(jié)點并選中它。單擊“動畫”>“粘貼”?并確保在底部面板的動畫編輯器中也打開帶有“A+”圖標的按鈕(加載時自動播放)和循環(huán)箭頭(動畫循環(huán))。就是這樣;?所有怪物現(xiàn)在都會播放浮動動畫。

我們可以根據(jù)生物的 改變播放速度random_speed。打開Mob的腳本并在函數(shù)末尾initialize()添加以下行。

func initialize(start_position, player_position):
    #...
    $AnimationPlayer.speed_scale = random_speed / min_speed

這樣,您就完成了第一個完整的 3D 游戲的編碼。

恭喜!

在下一部分中,我們將快速回顧您學到的內(nèi)容,并為您提供一些鏈接以繼續(xù)學習更多內(nèi)容。但現(xiàn)在,這里是完整的Player.gd,?Mob.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 movement being very fast
    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + direction,Vector3.UP)
        $AnimationPlayer.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 1

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

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse

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

func _on_mob_detector_body_entered(body):
    die()

還有 The?Mob的腳本。

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)

    $AnimationPlayer.speed_scale = random_speed / min_speed

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

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

更進一步?

您可以為自己完成了第一個使用 Godot 的 3D 游戲而感到欣慰。

在本系列中,我們介紹了廣泛的技術(shù)和編輯器功能。希望您已經(jīng)見證了 Godot 的場景系統(tǒng)是多么直觀,并學到了一些可以在您的項目中應用的技巧。

但我們只是觸及了表面:Godot 為您節(jié)省創(chuàng)建游戲的時間提供了更多。您可以通過瀏覽文檔了解所有這些。

你應該從哪里開始?在下面,您將找到幾頁以開始探索和構(gòu)建您目前所學的內(nèi)容。

但在此之前,這里有一個下載項目完整版本的鏈接:?https://github.com/godotengine/godot-3d-dodge-the-creeps。

瀏覽手冊?

每當您有疑問或?qū)δ稠椆δ芨械胶闷鏁r,手冊就是您的盟友。它不包含有關(guān)特定游戲類型或機制的教程。相反,它解釋了 Godot 的一般工作原理。在其中,您會找到有關(guān) 2D、3D、物理、渲染和性能等方面的信息。

以下是我們建議您接下來探索的部分:

  1. 閱讀腳本部分,了解您將在每個項目中使用的基本編程功能。

  2. 3D和物理部分將教您更多有關(guān)在引擎中創(chuàng)建 3D 游戲的信息。

  3. 輸入是任何游戲項目的另一個重要輸入。

您可以從這些開始,或者,如果您愿意,可以查看左側(cè)的側(cè)邊欄菜單并選擇您的選項。

我們希望您喜歡這個教程系列,我們期待看到您使用 Godot 取得的成就。文章來源地址http://www.zghlxwxcb.cn/news/detail-703038.html

到了這里,關(guān)于Godot引擎 4.0 文檔 - 第一個 3D 游戲的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Godot 4.0 遮罩一個2D物體,使其部分顯示

    Godot 4.0 遮罩一個2D物體,使其部分顯示

    本文針對Godot 4.0。 我也查到了Godot 3.5如何實現(xiàn)遮罩,見這個鏈接 https://ask.godotengine.org/3031/how-do-i-mask-a-sprite 由于查到的大部分教程均針對3.5版本,特此提供4.0版本的教程。 Godot4.0的遮罩不是一個單獨的節(jié)點,這個功能被包含在了一個常見的基類 CanvasItem 內(nèi)。 若要遮罩一個物體,可

    2024年02月08日
    瀏覽(161)
  • 關(guān)于Godot游戲引擎制作流水燈

    關(guān)于Godot游戲引擎制作流水燈

    先上核心代碼 ?游戲節(jié)點 ? ?流水燈的通途可以是 1. 裝飾 2. 音樂類多媒體程序(如FL中TB-303的步進燈) FL Studio Transistor Bass

    2024年02月14日
    瀏覽(93)
  • 關(guān)于游戲引擎(godot)對齊音樂bpm的技術(shù)

    引擎默認底層 1. _process(): 每秒鐘調(diào)用60次(無限的) 數(shù)學 1. bpm=1分鐘節(jié)拍數(shù)量=60s節(jié)拍數(shù)量 bpm120 = 60s120拍 2. 每拍子時間= 60/bpm 3. 每個拍子觸發(fā)周期所需要的幀數(shù)= 每拍子時間*60(幀率) 這個是從幀數(shù)級別上對齊拍子的時間,對于游戲引擎來說,用幀數(shù)更加適合高頻觸發(fā)程序 在

    2024年02月16日
    瀏覽(89)
  • Unity vs Godot :哪個游戲引擎更適合你?

    Unity vs Godot :哪個游戲引擎更適合你?

    游戲引擎的選擇對開發(fā)過程和最終產(chǎn)品質(zhì)量有著重大影響。近年來,Godot和Unity這兩款引擎受到廣泛關(guān)注。本文將從多個維度對兩者進行比較,以期為開發(fā)者提供正確的選擇建議。 Godot和Unity都有各自的優(yōu)勢,沒有絕對的好壞之分。Godot開源免費,上手簡單,更適合2D和小型游戲

    2024年01月23日
    瀏覽(106)
  • GODOT游戲引擎簡介,包含與unity性能對比測試,以及選型建議

    GODOT游戲引擎簡介,包含與unity性能對比測試,以及選型建議

    GODOT,是一個免費開源的3D引擎。本文以unity作對比,簡述兩者區(qū)別和選型建議。由于是很久以前寫的ppt,技術(shù)原因視頻和部分章節(jié)丟失了。建議當做業(yè)務(wù)參考。 GODOT目前為止遇到3個比較重大的機遇,第一個是oprea的合作獎,第二個是用支持c#換來的微軟的投資,第三個是虛幻

    2024年02月14日
    瀏覽(233)
  • 使用js原生customElements.define()API 實現(xiàn)類似godot游戲引擎的colorRect類

    一共有兩個方案,一個是基于div和css的dom渲染,一個是基于canvas的硬件繪圖

    2024年02月08日
    瀏覽(95)
  • Godot 4.0 加載為占位符(InstancePlaceholder)的用法和特點

    Godot 4.0 加載為占位符(InstancePlaceholder)的用法和特點

    加載為占位符 的功能設(shè)計上是用來輔助選擇性加載場景的。比如一個很龐大的3D世界,玩家一時之間只會處在世界一小部分區(qū)域內(nèi),同時讓整個地圖駐留于內(nèi)存是不現(xiàn)實的,此時需要選擇性地靈活加載地圖,使用Godot的\\\"加載為占位符\\\"功能可輔助這項工作的進行。 本人以開發(fā)2D游戲

    2024年02月03日
    瀏覽(23)
  • godot引擎c++源碼深度解析系列二

    記錄每次研究源碼的突破,今天已經(jīng)將打字練習的功能完成了一個基本模型,先來看下運行效果。 godot源碼增加打字練習的demo 這個里面需要研究以下c++的控件頁面的開發(fā)和熟悉,畢竟好久沒有使用c++了,先來看以下代碼吧。 就這樣就實現(xiàn)了文本框,輸入框和按鈕的實現(xiàn),以

    2024年02月15日
    瀏覽(24)
  • 基于Godot的康威的生命游戲

    在_ready()函數(shù)中創(chuàng)建了一個“滑翔機”,坐標系以左上角為原點 創(chuàng)建了一個定時器,每隔0.1秒調(diào)用一次_on_timer_timeout(),所以更新代碼也在這

    2024年02月17日
    瀏覽(20)
  • Unity 3D腳本編程與游戲開發(fā)【4.0】

    Unity 3D腳本編程與游戲開發(fā)【4.0】

    7.2.3 DOTween插件使??法簡介 緩動動畫既是?種編程技術(shù),也是?種動畫的設(shè)計思路。從設(shè)計?度來看,可以有以下描述。 ①事先設(shè)計很多基本的動畫樣式,如移動、縮放、旋轉(zhuǎn)、變?和彈跳等。但這些動畫都以抽象?式表?,?般封裝為程序函數(shù)。 ②動畫的參數(shù)可以在使?

    2024年04月09日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包