Sistema de turnos no Unity (Turn Based System in Unity) Part 2
- lucas pinto
- Aug 23, 2020
- 6 min read
Com o sistema de turnos funcionando, vamos agora fazer com que os participantes possam ver a lista de turnos.
Obs.: Esse post é a continuação de um post anterior. Nele nós fizemos a estrutura básica de um sistema de turnos de um jogo. Agora nós vamos apenas complementar dando um feedback na interface.

Aqui está um bom exemplo do que queremos fazer. No Dofus (de novo) os participantes podem ver quem está no turno atual e qual a ordem de turnos.

Em resumo aqui vamos primeiro montar os elementos visuais na tela, em seguida criar meios de modificar esses elementos visuais em código e por fim fazer com que esses elementos possam observar o Sistema de Batalha e corresponder visualmente de forma automática.
1 - Montando o Canvas e seus elementos (Setup Canvas and UI elements)
O primeiro passo para iniciarmos essa funcionalidade é montar na nossa cena o Canvas e todos os elementos que serão populados e atualizados para serem exibidos na tela.
Criei na hierarquia da cena um Game Object vazio nomeado UI e dentro dele um novo Canvas chamado Canvas Turn List, ele vai funcionar como o nosso "Controlador" que (mais pra frente) vai ficar observando o sistema de batalha.
Para evitar dor de cabeça, no componente Canvas Scaler desse novo Canvas modifique o parâmetro UI Scale Mode como Scale With Screen Size e assim teremos a lista sempre proporcional ao tamanho da tela.


Dentro do Canvas Turn List vamos criar um Panel simples chamado Turn List Panel que vai servir como Background/Container que vai ser populado com botões que vão representar todas as entidades com turnos no jogo. Posicione ele da forma que desejar (coloquei no topo da tela no exemplo).


Se quisermos que todos os botões dentro dele fiquem alinhados lado a lado, vamos adicionar o componente Horizontal Layout Group.

Além disso, para que o tamanho do painel se ajuste dinamicamente de acordo com a quantidade de botões (Entidades com Turnos), vamos adicionar também o componente Content Size Fitter.

Agora precisamos criar os botões que vão ser inseridos dentro do painel. No meu projeto o botão chama Item List Entity e tudo que ele precisa é ser um pouco menor do que o painel.

Você pode adicionar outros botões para ver se o painel está se comportando de acordo. Veja no exemplo como fica com três botões dentro do painel.

Agora crie uma pasta no seu projeto e jogue somente o botão Item List Entity nessa pasta para torná-lo um prefab.

Exceclente, temos os elementos visuais que precisamos agora é hora de "codar".
2 - UI Turn List Item
Precisamos de algo que possa nos dar controle sobre os botões, como texto e sua interação. Demais interações que possam ocorrer ao clicar no botão você pode colocar aqui, por exemplo, ao clicar no botão a câmera se move em direção àquela entidade e foca de perto.
Abra o prefab Item List Entity que acabamos de criar e dentro dele adicione um novo componente UI Turn List Item (script novo). No botão vamos precisar de (1) alterar o texto dele, (2) desabilitar interação e (3) habilitar interação. (2) e (3) são apenas para que possamos visualizar melhor a lista em tempo real.
UITurnListItem.cs
using UnityEngine;
using TMPro;
using UnityEngine.UI;
public class UITurnListItem : MonoBehaviour {
[SerializeField] private TMP_Text _tmpText;
[SerializeField] private Button _button;
private void Setup() {
_tmpText = GetComponentInChildren<TMP_Text>();
_button = GetComponent<Button>();
}
private void Start () {
Setup();
}
public void SetText(string newText) {
_tmpText.text = newText;
}
public void ClearFocus() {
_button.interactable = false;
}
public void Focus() {
_button.interactable = true;
}
}
Apesar de já termos tratados as dependências no Setup(), eu gosto de também vincular no prefab via inspector as dependências (quando isso é possível).

3 - UI Turn List Panel
A parte mais fácil disso tudo. Apenas criamos um componente UITurnListPanel e associamos ao GameObject do nosso painel. Ele é uma classe vazia:
using UnityEngine;
public class UITurnListPanel : MonoBehaviour{
}
Esteja ciente de que não é uma boa ideia ficar adicionando MonoBehaviours na cena, mas acredito para um único componente de interface vale o custo. Estamos fazendo isso para que o Canvas Turn List, que controla toda a UI, possa encontrar o GameObject do painel com facilidade e sem ambiguidade.

Isso para que possamos executar GetComponentInChildren<UITurnListPanel>() um nível acima e ter a certeza de que pegamos o painel que precisamos.
4 - UI Turn List
Finalmente, agora podemos programar o "coração" da nossa interface. A UI Turn List vai ter as seguintes responsabilidades:
Construir a Lista de turnos no início da cena
Perceber as alterações de turno que ocorrerem no sistema de batalha
Habilitar e desabilitar a interação com os botões de acordo com o turno atual
4.1 - Construir a Lista de Turnos no início da cena (Assembling the UI List)
Lembre-se que estamos fazendo tudo isso (sistema de turnos) para controlar uma lista e essa lista (turn list) guarda as informações mais fundamentais para esse sistema. Vamos então criar uma lista também na interface que vai refletir a turn list do sistema de batalha principal.
A diferença que a no sistema de batalha temos uma lista de Entidades com turno e na nossa interface a lista vai guardar somente botões (que vão representar as entidades).
UITurnList.cs - turnListItem + Start() + Setup()
public class UITurnList : MonoBehaviour
{
[SerializeField] List<UITurnListItem> turnListItem;
private void Setup() {
turnListItem = new List<UITurnListItem>();
}
private void Start () {
Setup();
}
}
Com essa Lista de botões, precisamos de alguma forma de adicionar em código esses botões para que eles comecem a aparecer na interface. Pra fazer isso precisamos ter o prefab do botão que criamos na etapa 1 e uma referência ao painel que criamos na etapa 3.
Disso estamos habilitados a adicionar itens na lista. O processo é simples: Instanciar o botão na cena como filho do painel, pegar o componente UITurnListItem que está nesse novo botão da cena e adicionar na nossa lista de botões (turnListItem).
UITurnList.cs - Setup() e AddItemList(string)
...
public GameObject uiItemListPrefab;
[SerializeField] private UITurnListPanel panelTurnList;
private void Setup() {
...
panelTurnList = GetComponentInChildren<UITurnListPanel>();
}
private void AddItemList(string itemText) {
if (panelTurnList == null) return;
GameObject uiItemListItem = Instantiate( uiItemListPrefab, panelTurnList.transform );
UITurnListItem listItem = uiItemListItem.GetComponent<UITurnListItem>();
listItem.SetText( itemText );
turnListItem.Add( listItem );
}
Outra funcionalidade importante é a ClearPanel() que vai deletar todos os botões no painel antes de adicionarmos:
UITurnList.cs - ClearPanel()
private void ClearPanel() {
foreach (Transform child in panelTurnList.transform)
GameObject.Destroy( child.gameObject );
}
Para construir agora a lista de botões vamos precisar apenas de uma referência do Sistema de Batalha para que possamos olhar a lista de lá e chamar o AddItemList() sem se esquecer é claro de limpar tudo antes por meio do ClearPanel();
UITurnList.cs - battleSystem + Start() + Setup() + StartCo() + Initialize()
...
[SerializeField] private BattleSystem battleSystem;
private void Setup() {
...
battleSystem = FindObjectOfType<BattleSystem>();
}
private void Start () {
...
StartCoroutine( StartCo() );
}
private IEnumerator StartCo() {
yield return new WaitForSeconds(0.25f); ;
Initialize();
}
private void Initialize() {
ClearPanel();
List<TurnEntity> turnListEntities = battleSystem.TurnList;
foreach (TurnEntity turnEntity in turnListEntities)
AddItemList( turnEntity.name );
}
Repara que em cima eu não chamei o método Initialize no Start. Isso porque em algumas situações pode ser o TurnList do sistema de batalha ainda não estar inicializado, se isso ocorrer o Initialize não vai poder cumprir a missão de "espelhar" a lista de turnos já que ela está vazia.
Ainda não encontrei alguma forma mais organizada de fazer isso, então a solução por agora é esperar mesmo com WaitForSeconds.
4.2 - Perceber as alterações do turno que ocorrerem no sistema de batalha (Listening for Battle System Events)
Precisamos agora encontrar alguma forma de fazer com que o nossos controlador de UI esteja atento e "escute" tudo que está acontecendo no sistema de batalha para reagir de acordo. Existem algumas formas de fazer isso, e pessoalmente acredito que a mais organizada é utilizando eventos.
A nossa abordagem aqui vai ser: A cada novo turno no sistema de batalha o nosso controlador da UI vai chamar uma função que vai atualizar os dados mas pra isso vamos criar esse evento no Sistema de batalha e disparar.
BattleSystem.cs - OnStartTurn + TurnLoopCo()
using UnityEngine.Events;
...
public UnityEvent OnStartTurn;
...
private IEnumerator TurnLoopCo () {
Debug.Log(">>>>>>> Battle Start <<<<<<<");
yield return new WaitForSeconds( 1f );
while (inBattle) {
currentEntity = turnList [ CurrentTurnIndex ];
if (OnStartTurn != null)
OnStartTurn.Invoke();
yield return currentEntity.Turn();
currentTurnIndex = GetNextTurnIndex( CurrentTurnIndex );
currentEntity = turnList [ CurrentTurnIndex ];
}
Debug.Log( ">>>>>>> Battle End <<<<<<<" );
}
...
Agora vamos criar a função UpdateUI() no Controller da nossa UI (UITurnList.cs) e em sequencia obter o evento OnStartTurn do Sistema de batalha e vincular a nossa função à esse evento.
UITurnList.cs - Setup() + UpdateUI()
private void Setup() {
...
battleSystem.OnStartTurn.AddListener( UpdateUI );
}
public void UpdateUI() {
}
Habilitar e desabilitar a interação com os botões de acordo com o turno atual
4.3 - Habilitar e desabilitar a interação com os botões de acordo com o turno atual (Updating Buttons interaction on event calls)
Para fechar agora basta uma nova função ClearFocus na UITurnList.cs que vai chamar o ClearFocus() de cada botão da nossa lista de botões, desabilitandoa interação com todos os botões.
Agora podemos colocar o código na UpdateUI() que vai limpar toda a interação e depois ativar a interação somente do botão referente à entidade com o turno atual
UITurnList.cs - ClearFocus() + UpdateUI()
private void ClearFocus() {
foreach (UITurnListItem listItem in turnListItem)
listItem.ClearFocus();
}
public void UpdateUI() {
ClearFocus();
turnListItem [ battleSystem.CurrentTurnIndex ].Focus();
}
Prontinho! Agora nosso controlador da interface deve ser capaz de dar todo o feedback visual que precisamos.




Gostou do Conteúdo?
Se você achou útil o que leu aqui, me ajuda também. Existem várias formas de você contribuir, seja baixando meu jogo, seja fazendo uma doação ou seja até mesmo comentando aqui o que achou.
Comments