Хорошо, я постараюсь написать это сообщение на португальском и английском языках.
Английский
Поехали!
Что такое SignalBus
и почему у меня в голове шумит, когда я думаю об этом паттерне и, что более важно, как реализовать его в Godot?
Итак, какова концепция этого паттерна?
Представьте себе ситуацию: вы сделали красивый и замечательный пользовательский интерфейс, построили уровень, у вас даже есть точки для спавна персонажа по всему уровню. Все идеально, затем вам нужно связать игрока и его очки жизни с пользовательским интерфейсом.
Первоначальная мысль приходит в голову: «Это работа для сигналов!», поэтому на своем уровне вы находите узел игрока, а затем соединяете его с UI. Все работает, но теперь у вас где-то есть фрагмент кода (уровня, игрока или UI), который вам нужно знать и организовать.
Таким образом, этот код запутывается.
Для небольших игр это не проблема, и, в частности, пока я не продвинулся в разработке, я не испытывал никаких проблем, и решение казалось мне даже элегантным.
Но у меня появился зуд в голове, этот шум в мозгу, это может быть лучше, но как?
Именно тогда я услышал термин SignalBus
. Теперь у нас есть проблема, и мы собираемся решить ее немного более элегантно, а затем подумать о последствиях этого.
В данном случае у нас есть Singleton
, что для движка делается просто созданием скрипта (который мы назовем SignalBus.gd
и добавим список автозагрузки, таким образом скрипт будет всегда запущен и его можно будет вызвать в любом другом скрипте.
Затем давайте добавим нашему другу сигнальной шине что-нибудь очень интересное, например:
#SignalBus.gd
extends Node2D
signal PlayerDamaged(total_hp, current_hp)
Довольно коротко, верно?
Но как насчет нашего друга-игрока?
#Player.gd
class_name Player extends KinematicBody2D
#...
func receive_damage(damage):
#...
SignalBus.emit("PlayerDamaged", max_hp, current_hp)
#...
#...
Как видите, класс игрока знает только о SignalBus, но он не знает пользовательского интерфейса, не знает уровня, и, честно говоря, ему это и не нужно. Уровню также больше не нужно знать игрока, не говоря уже о пользовательском интерфейсе.
Теперь давайте посмотрим на класс пользовательского интерфейса,
#UI.gd
class_name UI extends Control
#...
func _ready(damage):
#...
SignalBus.connect("PlayerDamaged", self, "on_update_player_life")
#...
func on_update_player_life(max, curr):
# Update ui with actual values
#...
Теперь у нас есть код, связанный только с SignalBus
, с помощью которого мы можем перемещать вещи в другие места и даже размещать больше вещей, слушающих сигнал игрока.
Представьте, что у нас теперь есть враг, который ждет, пока игрок получит урон, а затем стреляет? Или нам нужно сделать новую анимацию сотрясения экрана, когда игрок получает урон, все это можно сделать с помощью того же сигнала, которым управляет наш друг bus
.
Português
Vamos lá,
O que é um SignalBus
e porque todo esse barulho em minha cabeça quando penso nesse padrão e o mais importante, como implementar ele em Godot?
Какова идея?
Представьте себе ситуацию, вы сделали свой пользовательский интерфейс, чтобы он был красивым и чудесным, у вас есть свой уровень, чтобы его построить, у вас есть все предпосылки для того, чтобы нарисовать персонажа во время фазы. Все это идеально, но вам необходимо соединить jogador и его элементы жизни с пользовательским интерфейсом.
В своем первоначальном воображении вы думаете: «Это работа для синаев!», а затем на своем уровне вы локализуете nó jogador и подключаете его к пользовательскому интерфейсу. Все работает, но теперь у вас в любом месте (на уровне, в жогадоре или в пользовательском интерфейсе) есть тречо де кодиго, которое нужно знать и организовать.
Ou seja, acoplamento de código.
Для маленьких джогов это не является проблемой, в частности, при моем продвинутом развитии я не чувствовал проблем, и решение мне показалось очень элегантным.
Mas eu tinha essa pulga atrás da orelha pensando, isso pode ser melhor, mas como?
Foi então que ouvi o termo SignalBus
. Agora temos um problema e iremos a solução do problema um pouco mais elegante, e depois vamos pensar nas implicações disso.
В данном случае мы имеем Singleton
, который для движка можно реализовать, просто создав скрипт (который мы создадим из SignalBus.gd
и добавим список автозагрузок, таким образом, скрипт будет постоянно выполняться и может быть добавлен в любой другой скрипт.
Мы предлагаем вам добавить еще один интересный вариант для нашего друга — busão de sinais, смотрите пример:
#SignalBus.gd
extends Node2D
signal PlayerDamaged(total_hp, current_hp)
Bem curtinho, né?
Mas e o nosso amigo jogador?
#Player.gd
class_name Player extends KinematicBody2D
#...
func receive_damage(damage):
#...
SignalBus.emit("PlayerDamaged", max_hp, current_hp)
#...
#...
Как можно видеть, класс джогадоров знаком только с SignalBus, но не знаком с пользовательским интерфейсом, не знаком с уровнем, и, искренне говоря, не заинтересован в этом. Уровень там же не требует более глубокого знания jogador и гораздо меньшего знания UI.
Agora vmaos a classe do UI,
#UI.gd
class_name UI extends Control
#...
func _ready(damage):
#...
SignalBus.connect("PlayerDamaged", self, "on_update_player_life")
#...
func on_update_player_life(max, curr):
# Update ui with actual values
#...
Agora temos um código que está acoplado apenas no SignalBus
, com isso podemos mover coisas para outros lugares e inclusive colocar mais coisas ouvindo o sinal do player.
Представьте, что у нас теперь есть враг, который ждет, пока игрок получит урон, прежде чем выстрелить? Или нам нужно поместить на экран новую анимацию дрожания, когда игрок получает урон, все это можно сделать с помощью того же сигнала, которым управляет наш друг bus
.
Надеюсь, вам понравился этот фрагмент.
(()=>())()