The main game scene

Now it's time to bring everything we did together into a playable game scene.

Create a new scene and add a Node named Main. (The reason we are using Node instead of Node2D is because this node will be a container for handling game logic. It does not require 2D functionality itself.)

Click the Instance button (represented by a chain link icon) and select your saved Player.tscn.

../../_images/instance_scene.png

現在,將下列節點新增為 Main 的子節點,並他們的名稱如下(數值單位為秒):

  • Timer (名稱為 MobTimer )——用以控制怪物產生的頻率

  • Timer (名稱為 ScoreTimer )——用以每秒增加成績

  • Timer (名稱為 StartTimer )——用以在開始前延遲

  • Position2D (名為 StartPosition )——用以判斷玩家的起始位置

將每個 Timer 節點的 Wait Time (等待時間)屬性按照如下設定:

  • MobTimer: 0.5

  • ScoreTimer: 1

  • StartTimer: 2

另外,將 StartTimerOne Shot 屬性設為「開啟」,並將 StartPosition 節點的 Position 設為 (240, 450)

產生怪物

Main 節點會產生新的怪物,而我們希望這些怪物出現在畫面邊緣上不同的位置。新增一個 Path2D 節點作為 Main 的子節點,設定名稱為 MobPath 。之後當選擇 Path2D 的時候,編輯器上方會出現一些新的按鈕:

../../_images/path2d_buttons.png

選擇中間的按鈕 (「新增控制點」),然後點擊顯示的轉角處來新增控制點並繪製路徑。若要將控制點吸附到網格上,請開啟「使用網格吸附」與「使用吸附」選項,這兩個選項在「鎖定」按鈕的左邊,顯示的圖示是一個磁鐵跟幾條相交的線。

../../_images/grid_snap_button.png

重要

請確定以 順時針 方向繪製路徑,不然的話怪物會 向外 產生而不是 向內 產生!

../../_images/draw_path2d.gif

在圖片內放置了點 4 以後,點擊「關閉曲線」按鈕,就可以完成繪製曲線。

現在路徑已經定義好了,接著我們新增一個 PathFollow2D 節點作為 MobPath 的子節點,並將其命名為 MobSpawnLocation 。這個節點會在移動的時候自動旋轉並跟著路徑移動,所以我們可以用 MobPath 來在路徑上隨機選擇位置與方向。

現在場景看起來會這樣:

../../_images/main_scene_nodes.png

Main(主要)腳本

Main 新增一個腳本。在腳本的頂部,我們會用 export (PackedScene) 來選擇要實體化的 Mob 場景。

extends Node

export(PackedScene) var mob_scene
var score

We also add a call to randomize() here so that the random number generator generates different random numbers each time the game is run:

func _ready():
    randomize()

Click the Main node and you will see the Mob Scene property in the Inspector under "Script Variables".

要為這個屬性賦值有兩個方法:

  • 從「檔案系統」面板中拖移 Mob.tscnMob 屬性來。

  • 點擊「[空]」旁邊的下拉箭頭,並選擇「載入」。接著選擇 Mob.tscn

接著,從場景 Dock 中選擇 Player 節點,然後前往側欄的節點 Dock 中。節選選擇節點 Dock 中的 [訊號] 分頁。

接著應該可以看到所有 Player 節點的訊號。點兩下列表中的 hit 訊號 (或是右鍵點擊然後選擇 [連接...])。這樣便可以打開訊號連接對話框。這裡我們要把新函式命名為 game_over ,這個函式會負責處理遊戲結束時要做的事。在 [連接訊號] 視窗中的 [Receiver 方法] 內輸入「game_over」,並點擊 [連接]。將下列程式碼加到新建立的函式當中,接著新增一個 new_game 函式,負責在遊戲開始時搞定一切:

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()

現在,將各個 Timer 節點( StartTimerScoreTimer 、與 MobTimer )的 timeout() 訊號連接到 Main 腳本中。 StartTimer 會啟動另外兩個 Timer。 ScoreTimer 會以 1 為單位增加分數。

func _on_ScoreTimer_timeout():
    score += 1

func _on_StartTimer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

In _on_MobTimer_timeout(), we will create a mob instance, pick a random starting location along the Path2D, and set the mob in motion. The PathFollow2D node will automatically rotate as it follows the path, so we will use that to select the mob's direction as well as its position. When we spawn a mob, we'll pick a random value between 150.0 and 250.0 for how fast each mob will move (it would be boring if they were all moving at the same speed).

需要注意的是,新建立的節點必須要使用 add_child() 來將節點新增到場景中。

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

    # Choose a random location on Path2D.
    var mob_spawn_location = get_node("MobPath/MobSpawnLocation")
    mob_spawn_location.offset = randi()

    # Set the mob's direction perpendicular to the path direction.
    var direction = mob_spawn_location.rotation + PI / 2

    # Set the mob's position to a random location.
    mob.position = mob_spawn_location.position

    # Add some randomness to the direction.
    direction += rand_range(-PI / 4, PI / 4)
    mob.rotation = direction

    # Choose the velocity for the mob.
    var velocity = Vector2(rand_range(150.0, 250.0), 0.0)
    mob.linear_velocity = velocity.rotated(direction)

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

重要

Why PI? In functions requiring angles, Godot uses radians, not degrees. Pi represents a half turn in radians, about 3.1415 (there is also TAU which is equal to 2 * PI). If you're more comfortable working with degrees, you'll need to use the deg2rad() and rad2deg() functions to convert between the two.

測試場景

Let's test the scene to make sure everything is working. Add this new_game call to _ready():

func _ready():
    randomize()
    new_game()

同時,將 Main 設為「主要場景」——也就是遊戲啟動後會自動執行的場景。點擊「執行」按鈕,當提示框出現後選擇 Main.tscn

小訣竅

If you had already set another scene as the "Main Scene", you can right click Main.tscn in the FileSystem dock and select "Set As Main Scene".

現在應該可以到處移動玩家了,並會看到怪物產生出來。玩家在被怪物撞到後會消失。

確定好了所有東西都正常後,將呼叫 new_game() 的程式碼從 _ready() 裡刪除。

What's our game lacking? Some user interface. In the next lesson, we'll add a title screen and display the player's score.