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.

Счет и повтор

В этой части мы добавим счёт, воспроизведение музыки и возможность перезапуска игры.

Нам нужно отслеживать текущий счёт в переменной и выводить его на экран с помощью минимального интерфейса. Для этого мы будем использовать текстовую метку.

In the main scene, add a new child node Control to Main and name it UserInterface. You will automatically be taken to the 2D screen, where you can edit your User Interface (UI).

Add a Label node and name it ScoreLabel

image1

В инспекторе установите для параметра метки Text значение "Score: 0".

image2

Кроме того, по умолчанию текст белый, как и фон нашей игры. Нам нужно изменить его цвет, чтобы увидеть его во время выполнения.

Scroll down to Theme Overrides, and expand Colors and enable Font Color in order to tint the text to black (which contrasts well with the white 3D scene)

image3

Наконец, нажмите и перетащите текст в области просмотра, чтобы переместить его от левого верхнего угла.

image4

The UserInterface node allows us to group our UI in a branch of the scene tree and use a theme resource that will propagate to all its children. We'll use it to set our game's font.

Создание темы интерфейса

Once again, select the UserInterface node. In the Inspector, create a new theme resource in Theme -> Theme.

image5

Нажмите на него, чтобы открыть редактор темы в нижней панели. Он даёт вам предварительный просмотр того, как все встроенные виджеты пользовательского интерфейса будут выглядеть с вашим ресурсом темы.

image6

По умолчанию тема имеет только одно свойство - Default Font (Шрифт по умолчанию).

См.также

Вы можете добавить больше свойств к ресурсу темы для разработки сложных пользовательских интерфейсов, но это выходит за рамки данной серии статей. Чтобы узнать больше о создании и редактировании тем, смотрите Introduction to GUI skinning.

This one expects a font file like the ones you have on your computer. Two common font file formats are TrueType Font (TTF) and OpenType Font (OTF).

In the FileSystem dock, expand the fonts directory and click and drag the Montserrat-Medium.ttf file we included in the project onto the Default Font. The text will reappear in the theme preview.

The text is a bit small. Set the Default Font Size to 22 pixels to increase the text's size.

image7

Отслеживание счета

Let's work on the score next. Attach a new script to the ScoreLabel and define the score variable.

extends Label

var score = 0

The score should increase by 1 every time we squash a monster. We can use their squashed signal to know when that happens. However, because we instantiate monsters from the code, we cannot connect the mob signal to the ScoreLabel via the editor.

Вместо этого нам придётся создавать связь из кода каждый раз, когда мы порождаем монстра.

Open the script main.gd. If it's still open, you can click on its name in the script editor's left column.

image8

Alternatively, you can double-click the main.gd file in the FileSystem dock.

At the bottom of the _on_mob_timer_timeout() function, add the following line:

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

This line means that when the mob emits the squashed signal, the ScoreLabel node will receive it and call the function _on_mob_squashed().

Head back to the ScoreLabel.gd script to define the _on_mob_squashed() callback function.

Там мы увеличиваем счёт и обновляем отображаемый текст.

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

The second line uses the value of the score variable to replace the placeholder %s. When using this feature, Godot automatically converts values to string text, which is convenient when outputting text in labels or when using the print() function.

См.также

You can learn more about string formatting here: Форматирование строки GDScript. In C#, consider using string interpolation with "$".

Теперь вы можете играть в игру и раздавить несколько врагов, чтобы увидеть, как увеличивается счёт.

image9

Примечание

В сложной игре вы можете захотеть полностью отделить пользовательский интерфейс от игрового мира. В этом случае вы не будете отслеживать счёт на метке. Вместо этого вы, возможно, захотите хранить его в отдельном, выделенном объекте. Но когда вы создаёте прототип или ваш проект прост, вполне можно ограничиться простым кодом. Программирование - это всегда баланс.

Повторная попытка игры

Теперь мы добавим возможность играть снова после смерти. Когда игрок умирает, мы выводим сообщение на экран и ждём ввода.

Head back to the main.tscn scene, select the UserInterface node, add a child node ColorRect, and name it Retry. This node fills a rectangle with a uniform color and will serve as an overlay to darken the screen.

To make it span over the whole viewport, you can use the Anchor Preset menu in the toolbar.

image10

Откройте его и примените команду Full Rect.

image11

Nothing happens. Well, almost nothing; only the four green pins move to the corners of the selection box.

image12

This is because UI nodes (all the ones with a green icon) work with anchors and margins relative to their parent's bounding box. Here, the UserInterface node has a small size and the Retry one is limited by it.

Select the UserInterface and apply Anchor Preset -> Full Rect to it as well. The Retry node should now span the whole viewport.

Let's change its color so it darkens the game area. Select Retry and in the Inspector, set its Color to something both dark and transparent. To do so, in the color picker, drag the A slider to the left. It controls the color's Alpha channel, that is to say, its opacity/transparency.

image13

Next, add a Label as a child of Retry and give it the Text "Press Enter to retry." To move it and anchor it in the center of the screen, apply Anchor Preset -> Center to it.

image14

Кодирование опции повторной попытки

We can now head to the code to show and hide the Retry node when the player dies and plays again.

Open the script main.gd. First, we want to hide the overlay at the start of the game. Add this line to the _ready() function.

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

Затем, когда игрок получает удар, мы показываем затенение.

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

Finally, when the Retry node is visible, we need to listen to the player's input and restart the game if they press enter. To do this, we use the built-in _unhandled_input() callback, which is triggered on any input.

If the player pressed the predefined ui_accept input action and Retry is visible, we reload the current scene.

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

Функция get_tree() предоставляет нам доступ к глобальному объекту SceneTree, который позволяет нам перезагрузить и перезапустить текущую сцену.

Добавление музыки

Чтобы добавить музыку, которая непрерывно играет на заднем плане, мы воспользуемся другой функцией в Godot: autoloads.

To play audio, all you need to do is add an AudioStreamPlayer node to your scene and attach an audio file to it. When you start the scene, it can play automatically. However, when you reload the scene, like we do to play again, the audio nodes are also reset, and the music starts back from the beginning.

Вы можете использовать функцию автозагрузки, чтобы Godot автоматически загружал узел или сцену в начале игры, вне текущей сцены. Вы также можете использовать её для создания глобально доступных объектов.

Create a new scene by going to the Scene menu and clicking New Scene or by using the + icon next to your currently opened scene.

image15

Click the Other Node button to create an AudioStreamPlayer and rename it to MusicPlayer.

image16

Мы включили музыкальный файл House In a Forest Loop.ogg в каталог art/. Щёлкните и перетащите его на свойство Stream в инспекторе. Также включите Autoplay, чтобы музыка автоматически проигрывалась в начале игры.

image17

Сохраните сцену как MusicPlayer.tscn.

Мы должны зарегистрировать его как автозагрузку. Перейдите в меню Project -> Project Settings… и нажмите на вкладку Autoload.

В поле Path введите путь к вашей сцене. Щёлкните на значке папки, чтобы открыть браузер файлов, и дважды щёлкните на MusicPlayer.tscn. Затем нажмите кнопку Add справа, чтобы зарегистрировать узел.

image18

MusicPlayer.tscn now loads into any scene you open or play. So if you run the game now, the music will play automatically in any scene.

Прежде чем мы завершим этот урок, вот краткий обзор того, как это работает под капотом. Когда вы запускаете игру, ваша панель Scene изменяется, чтобы показать вам две вкладки: Remote и Local.

|изображение19|

Вкладка Remote позволяет визуализировать дерево узлов запущенной игры. Здесь вы увидите узел Main и всё, что содержит сцена, а также экземпляры мобов в нижней части.

|изображение20|

At the top are the autoloaded MusicPlayer and a root node, which is your game's viewport.

На этом мы закончим этот урок. В следующей части мы добавим анимацию, чтобы игра выглядела и ощущалась гораздо приятнее.

Here is the complete main.gd script for reference.

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