Unity e loops esperando resposta de outro script

131 views
Skip to first unread message

George Lucas

unread,
Jun 5, 2012, 6:35:28 PM6/5/12
to gamedev-u...@googlegroups.com
Estou com 2 pequenos problemas de implementação aqui, agradeceria muito se alguém mais experiente pudesse ajudar! Podem ser coisas até simples de repente, mas é melhor perguntar antes de bater a cabeça por mais horas com isso.

1- Pra quem não sabe, nosso grupo está tentando fazer um jogo de RPG tático, com movimentação pelo grid. Mas tem 2 scripts que não consigo fazer com que se comuniquem: um deles está ligado ao controlador do jogo (controla os estados) e outro a cada instância de tile presente no grid. Estou tentando fazer com que o grid seja apresentado ao usuário e dentre as casas a disposição dele (as primeiras 2 fileiras) ele escolher onde posicionar suas tropas antes de começar o jogo.

O problema está exatamente aí: a única forma encontrada de instanciar o player no grid assim é no evento OnMouseDown() do script ligado aos tiles - assim é fácil pegar onde o jogador clicou e saber onde deve-se instanciar um boneco. A questão é que não basta pegar a instância do script ligado ao controlador e deixar um pedaço dele em loop até atualizar uma certa variavel nele (via um setter), porque assim o Unity trava e impossibilita qualquer análise. Creio que a solução possa estar na utilização do IEnumerator e das Coroutines, mas tá difícil lidar com esses iteradores - to tentando chamar uma função gambiarra() pela StartCoroutine() q só dá um yield return 1 ou yield return 0 dependendo se o valor da variavel (posição) em questão é (0,0,0) ou se foi atualizada pelo outro script, mas ainda assim o Unity continua travando.

Sei que tá meio confuso, mas a pergunta importante é: como fazer um script esperar a resposta de outro sem o Unity travar? 


2- Por que diabos a função Start() não é a primeira a entrar em ação sempre? Esse script aí de cima ligado ao controlador é chamado por outro também ligado ao controlador dessa forma:

------------------------------------------------------------------------------------------------------
CriaPersonagem cp = GetComponent<CriaPersonagem>();
players = cp.Run(width, height);                //retorna uma lista dos players criados
------------------------------------------------------------------------------------------------------

E botando um breakpoint na função Start() do CriaPersonagem e outro na função Run(), dá pra notar que a função Run() roda antes da função Start()! E como preciso de coisas na função Start() antes de executar o Run(), o Unity fica soltando NullReferenceExceptions. Tava achando que na chamada do GetComponent() o Unity trataria de chamar o Start() no script, mas pelo visto não é isso que ocorre - e o mais estranho é ele chamar o Start() depois do Run(), do nada, sem motivo aparente. Alguém já passou por isso?

Precisando dos códigos em si, é só falarem.

[]s

Tinnus

unread,
Jun 5, 2012, 8:00:42 PM6/5/12
to gamedev-u...@googlegroups.com
2012/6/5 George Lucas <geoc...@gmail.com>:
> O problema está exatamente aí: a única forma encontrada de instanciar o
> player no grid assim é no evento OnMouseDown() do script ligado aos tiles -
> assim é fácil pegar onde o jogador clicou e saber onde deve-se instanciar um
> boneco. A questão é que não basta pegar a instância do script ligado ao
> controlador e deixar um pedaço dele em loop até atualizar uma certa variavel
> nele (via um setter), porque assim o Unity trava e impossibilita qualquer
> análise. Creio que a solução possa estar na utilização do IEnumerator e das
> Coroutines, mas tá difícil lidar com esses iteradores - to tentando chamar
> uma função gambiarra() pela StartCoroutine() q só dá um yield return 1 ou
> yield return 0 dependendo se o valor da variavel (posição) em questão é
> (0,0,0) ou se foi atualizada pelo outro script, mas ainda assim o Unity
> continua travando.

Uma corotina não pode "retornar" um valor no sentido normal da
palavra. Ela só retorna um valor especial que indica quando o fluxo de
execução deve retornar a ela. Então você pode fazer, por exemplo...

yield return null -- volta a executar no próximo frame
yield return new WaitForEndOfFrame() -- volta a executar no fim do
frame atual (junto com os LateUpdates)
yield return new WaitForSeconds(x) -- volta a executar daqui a x segundos

Sendo que ela vai voltar a execução da linha seguinte à linha do yield.

De qualquer forma, não parece que o que você quer fazer deve usar
isso. Corotinas em geral são boas para gerencias ações que levam mais
de um frame (por exemplo, o personagem andar de um tile a outro) ou
para atrasar algum tipo de processamento que não pode ser feito no
momento.

Me parece que a solução para o seu problema (sem mudar muito a idéia)
é simplesmente chamar um método
GameController.CriarJogadorEmTile(tile). Para isso você tem que ter
uma referência ao GO do controlador no script do tile e pegar o
componente GameController dele, ou então guardar de cara o
GameController.

Outro jeito de fazer, usando a idéia do controller ficar "esperando"
uma variável, é usar o Update(). Como ele é executado todo frame, você
pode fazer um if(tile_foi_clicado) { //fazer coisas } dentro do
Update. Como ele roda todo frame, comporta vários comportamentos do
tipo "verificar se algo aconteceu, e caso tenha acontecido, fazer
coisas".

> 2- Por que diabos a função Start() não é a primeira a entrar em ação sempre?
> Esse script aí de cima ligado ao controlador é chamado por outro também
> ligado ao controlador dessa forma:
>
> ------------------------------------------------------------------------------------------------------
> CriaPersonagem cp = GetComponent<CriaPersonagem>();
> players = cp.Run(width, height);                //retorna uma lista dos
> players criados
> ------------------------------------------------------------------------------------------------------
>
> E botando um breakpoint na função Start() do CriaPersonagem e outro na
> função Run(), dá pra notar que a função Run() roda antes da função Start()!
> E como preciso de coisas na função Start() antes de executar o Run(), o
> Unity fica soltando NullReferenceExceptions. Tava achando que na chamada do
> GetComponent() o Unity trataria de chamar o Start() no script, mas pelo
> visto não é isso que ocorre - e o mais estranho é ele chamar o Start()
> depois do Run(), do nada, sem motivo aparente. Alguém já passou por isso?

O Start não é um construtor e não é chamado imediatamente quando algo
é instanciado ou acessado, mas sim durante o começo do primeiro frame
em que o GameObject existe na cena. Sendo assim, o código de um
Start() não pode depender de que o Start() de outro objeto tenha sido
chamado. Uma solução rápida para isso é colocar o comportamento do
objeto a ser inicializado antes no Awake(), que é chamado um pouco
antes do Start() para todos os objetos.

E pra ficar claro, o GetComponent não inicializa nada nem gera
qualquer tipo de ação! Ele apenas retorna uma referência para o objeto
(as in OOP, não GameObject) da classe especificada (no caso o
CriaPersonagem) que esteja funcionando como um componente para o
GameObject atual.

Então, o ciclo de criação e correspondência entre GOs, componentes e
afins quando um GO é criado é o mais ou menos seguinte:

- A unity "sabe" (por estruturas internas dela, geradas no editor) que
um certo GameObject, ou seja, um objeto na cena ou um Prefab, tem
atrelado a ele uma lista de componentes. Um componente nada mais é do
que uma instância de uma classe que deriva de Component (lembrando que
MonoBehavior, e logo todos os scripts que você faz por padrão, derivam
de Component também).
- Internamente, para cada componente que um GO tem, a Unity guarda
(serializado) o tipo desse componente (Transform, SphereCollider,
CriaPersonagem) e todos os parâmetros expostos no Inspector para
aquela classe (por padrão, todas as variáveis públicas, recursivamente
no caso do tipo de uma variável pública ser outra classe).
- Quando um GameObject é instanciado na cena (o que, IMPORTANTE, no
editor pode acontecer várias vezes em momentos aleatórios), as
seguintes coisas acontecem:
---> Estruturas internas da Unity são atualizadas para refletir que um
novo GameObject está na cena e uma instância da classe GameObject é
criada.
---> A lista serializada de componentes é lida (provavelmente da
memória, em algum lugar interno da engine). Para cada componente
registrado para aquele objeto, a classe correspondente é instanciada
(chamando o construtor da mesma) e suas variáveis públicas são setadas
de acordo com os valores serializados.
- Até agora tudo acontece imediatamente na hora em que o GO é
instanciado (por exemplo, com um Instantiate que pode ser no meio de
um frame ou durante o carregamento da cena). Observem que até agora
nada de Start, Awake ou qualquer outra coisa.
- No começo do próximo frame, SE NÃO ESTIVERMOS NO EDITOR, é chamado
Awake(), em todos os componentes que implementem esse método, nos
novos objetos (criados desde o frame anterior) e, em seguida
(intercalados com coisas da engine) é chamado Start() da mesma forma.
- Tanto no editor quanto em runtime, e sempre que houver uma
recompilação de scripts durante a execução do jogo, é chamado também o
OnEnable() da mesma forma em todos os componentes.
- Mais pro meio do frame, o primeiro Update() do novo objeto é chamado
"junto" (em uma ordem aleatória, na verdade) e da mesma forma que de
todos os outros GOs, e a vida segue normalmente.

--
Bruno "Tinnus"

Tinnus

unread,
Jun 6, 2012, 1:18:03 PM6/6/12
to gamedev-u...@googlegroups.com
Adendo: acabei de passar por uma situação aqui, e parece que o
OnEnable() é chamado imediatamente depois da instanciação do objeto, e
não apenas no próximo frame como o Awake() e o Start().

2012/6/5 Tinnus <tin...@gmail.com>:
--
Bruno "Tinnus"

George Lucas

unread,
Jun 6, 2012, 1:56:16 PM6/6/12
to gamedev-u...@googlegroups.com
Obrigado, Tinnus!

Consegui resolver o (1) gambiarrando o Update() mesmo (tava evitando fazer isso ao máximo, mas não achei outro jeito - a primeira opção que você sugeriu era o que vinha tentando mas não consegui achar um jeito de fazer isso funcionar). Já o (2) foi questão de mudar a ordem das coisas e botar algumas inicializações no Awake() mesmo, mas deu pra ter uma ideia melhor do fluxo seguido pelo Unity que não consegui achar na documentação oficial. Você sabe essas coisas por experiência, procuras na comunidade ou vendo algum lugar da documentação oficial que eu por ser meio cego não consegui achar? 

[]s

2012/6/6 Tinnus <tin...@gmail.com>

Yanko Gitahy Oliveira

unread,
Jun 6, 2012, 2:08:31 PM6/6/12
to gamedev-u...@googlegroups.com
Provavelmente uma mistura disso tudo. Mas o Tinnus é daqueles que lê a API inteira auhuahuah

Isso é o tipo de detalhe que você só aprende usando a ferramenta com frequência (vide o adendo dele no último email, que foi uma coisa que ele acabou de ver olhando o código aqui); além do fato do mindset de trabalhar com componentes ser um pouco diferente de como a gente tá acostumado a pensar programando pra faculdade, por exemplo.

[]s

--Yanko

2012/6/6 George Lucas <geoc...@gmail.com>

Tinnus

unread,
Jun 6, 2012, 2:58:06 PM6/6/12
to gamedev-u...@googlegroups.com
É basicamente o que o Yanko falou mesmo, uma mistura de tudo. Boa
parte do conhecimento menos óbvio e mais importante está escondido no
UnityAnswers e no fórum, que costumam ser as primeiras ocorrências
quando se procura "unity coisas" no Google. Então, na dúvida, uma
busca do tipo costuma funcionar bem.

2012/6/6 George Lucas <geoc...@gmail.com>:
--
Bruno "Tinnus"
Reply all
Reply to author
Forward
0 new messages