O TEMA DO FÓRUM ESTÁ EM MANUTENÇÃO. FEEDBACKS AQUI: ACESSAR

Desenvolvendo um jogo de quebra-cabeças - Parte 2

Iniciado por MayLeone, 23/01/2019 às 16:29


<< Parte 1
No tutorial anterior nós começamos a desenvolver o jogo de quebra-cabeças recortando as peças de acordo com um tamanho em grade, e criando diversos comandos para gerenciar o game, porém nesse tutorial iremos desenvolver a parte prática do jogo, acompanhe:



Script da peça:
Temos agora as peças e grade em cena, mas elas não possuem nenhuma interação com o usuário.
Vamos então criar o script responsável por "controlarmos" as peças, crie um novo c# script com o nome de "PieceScript".
Vamos então criar nossas variáveis públicas (que serão acessadas a partir de outra classe), portanto terão como atributo "HiddenInInspector": duas variáveis do tipo Vector2 uma chamada "startPosition" e outra com o nome de "endPosition", e duas variáveis do tipo bool, uma chamada "canMove" que vai verificar se essa peça pode se mover e outra "cancelPiece" que vai verificar se essa peça parou de ser manipulada.
Como variáveis privadas nós teremos a variável do tipo SpriteRenderer com o nome de "sprite" e uma outra do tipo float chamada "timeToLerp" para criarmos um efeito "Lerp" ao soltar a peça, essa variável irá iniciar com o valor 20.
Dentro do Start do script inicie a variável ''sprite'' com a obtenção do componente "SpriteRenderer" do objeto:



A variável "startPosition" vai ser responsável por informar ao programa qual é a posição em que a peça foi instanciada, portanto, salve esse script e volte ao script "CropTexture" e antes de criar a grade dentro do método "CreatePiece", através da variável "quad" obtenha o script "PieceScript" e faça acesso à variável "StartPosition" definindo a variável "position":



Já a variável "EndPosition" é responsável por informar ao programa qual é a parte da grade correta para que a peça seja posicionada, então é crucial que você vá ao método "CreateGrid" e através do parâmetro "quad" faça acesso ao script "PieceScript" e à propriedade "endPosition" da peça, fazendo com que receba a posição (transform.position) da grade que acabou de ser criada:



Salve o script.
Vamos agora criar um script chamado "GameManager" que vai conter a score atual do jogador, o score que ele deve obter e a peça atual que está sendo manipulada.
Crie esse script com três variáveis públicas e estáticas, uma do tipo GameObject chamada "currentPiece" e duas do tipo int uma chamada "currentScore" e "scoreTotal". Salve o script:



Volte para o script "CropTexture" e no método "StartComponents" faça com que "currentScore" inicie em zero e com que "scoreTotal" receba a quantidade de peças vezes ela mesma:



Salve esse script e volte para o script "PieceScript".

Clicando na peça:
Após fazer esses pequenos ajustes, vamos fazer de fato a peça ser "levada" pelo mouse, ao clicar na mesma.
Defina o evento "OnMouseOver" no script "PieceScript" para ser disparado quando o ponteiro do mouse estiver sob o objeto da peça. Dentro do método crie uma condição que vai verificar o lado do mouse que foi pressionado, caso for o esquerdo(0) e cancelPiece for false e currentPiece for null (não há no momento nenhuma outra peça sendo manipulada pelo mouse), faremos com que currentPiece receba esse gameObject(essa peça) e que canMove fique true.
Caso o lado do mouse direito for pressionado (1), cancelPiece for false e canMove for true, iremos fazer com que cancelPiece receba true, pois com o lado direito do mouse nós iremos "soltar" a peça:



Agora dentro do Update do script nós vamos criar uma validação se "canMove" for true, e caso for, a peça poderá seguir o ponteiro do mouse.
Primeiro faça com que a sortingOrder do sprite fique igual a 1 (para ficar sobre todas as outras peças), e então crie uma variável local chamada mouseP para receber a posição do mouse de acordo com o mousePosition.
vamos ajustar o eixo 'z' da posição do mouse para as definições da tela do jogo(Mundo do Unity) e não com relação à tela do computador, para isso, basta fazer com que mouseP.z receba a posição "z" da peça menos a posição "z" da câmera.
Então finalmente faça com que o transform.position do objeto(peça) receba a posição do mouse (mouseP) com relação ao mundo dentro do Unity:



Através desses comandos, é possível que o sprite do objeto siga o ponteiro do mouse pela tela.

Faremos agora a função de cancelamento da peça, caso o jogador queira "largá-la".
Crie um método sem retorno chamado "CancelPiece" e dentro dele faça com que currentPiece receba 'null' (pois não estamos mais manipulando nenhuma peça), e faça com que a posição da peça volte para sua posição inicial com uma certa suavização utilizando o método "MoveTowards", saindo da posição atual onde o objeto se encontra, tendo como destino o Vector "startPosition" num tempo de acordo com a variável "TimeToLerp" multiplicado por time.DeltaTime.
Faça também com que a variável "canMove" fique false e depois uma validação se a posição do objeto é igual à posição "startPosition", caso for, faça com que o sortingOrder volte a ser 0 e que cancelPiece volte a ficar false:



Dentro do Update faça com que CancelPiece seja chamado se "cancelPiece" for verdadeiro:



Salve o script e o adicione ao prefab "Piece". Agora ao testar o jogo, você percebe que pode fazer com que a peça siga o mouse ao clicar na mesma, e se clicar com o lado direito, a peça se 'solta' e volta para seu ponto de origem suavemente:



Clicando na grade:
Agora que podemos transitar com a peça pela tela através do mouse, vamos criar o script da grade que irá verificar se a peça que estamos manipulando faz parte daquele pedaço da grade.
Crie um novo C# script com o nome de "GridScript".
Dentro do Update desse script, vamos verificar se o mouse está sob o colisor desse objeto (grade) , então crie uma validação através do BoxCollider2D do objeto, e do método "OverlapPoint, passando como argumento a posição do mouse de acordo com o mundo dentro do Unity.
Caso o mouse esteja sob esse collider, chame o método "Check" que iremos definir a seguir.

O método "Check" deve ser sem retorno, e dentro dele vamos verificar se o botão esquerdo o mouse foi pressionado (0) e se currentPiece não é nulo, ou seja, temos alguma peça sendo manipulada no momento.
Caso ambas sentenças forem verdadeiras, iremos criar uma segunda condição que verifica se "endPosition" de "currentPiece" é igual a posição dessa parte da grade, caso sim, iremos posicionar a peça exatamente na mesma posição que esse pedaço da grade, faremos com que o sortingOrder de "currentPiece" volte a ser 0, destruiremos o script "PieceScript" do "currentPiece" para que não possamos mais clicar na peça e movimentá-la, faremos com que currentPiece volte a ser nulo, incrementamos o score do jogador e por fim, destruímos o objeto da grade (Para evitar de clicar nela novamente e melhorar a performance do jogo, já que não iremos mais usar esse script).

Caso a peça corrente não tenha como 'destino' essa parte da grade, apenas faça com que ela "volte" para seu local de origem fazendo com que "cancelPiece" fique true.
O script deve estar assim:



Salve o script e adicione-o no prefab "Grid". Teste o jogo e veja que agora é possível encaixar as peças:



Verificando final de jogo:
Para verificar o final do jogo é bem simples: Primeiro crie um Canvas com um text ancorado e devidamente dimensionado com o texto "Congratulations":



E por padrão deixe-o desativado.
Agora vá para o script "GameManager" e crie uma variável pública do tipo "Text" chamada "text" (referencie a biblioteca "UI" dentro do script!)
Agora dentro do Update crie uma validação se currentScore for igual a totalScore, o gameObject de "text" irá ser ativado:



Agora anexe esse script no objeto "GameManager" e faça com que o text do canvas seja armazenado na variável "text" desse script.
Teste o jogo e tente montar o quebra-cabeças: Perceba que quando você o completa o jogo "se encerra" e a mensagem de congratulações aparece na tela:



Mostrando imagem original:
Outro sistema que podemos fazer é o de mostrar a imagem como ela é quando montada, ao clicar num botão.
É muito fácil criar esse sistema, basta criar uma imagem dentro do Canvas e a posicionar, ancorar e redimensionar da forma que achar melhor:



Desative a imagem também.
Agora vá para o script "CropTextures" crie uma variável pública do tipo Image chamada "img" (referencie a biblioteca UI) e no método "StartComponents" faça com que ela receba a textura da imagem do quebra-cabeças de acordo com sourceTexture, igual já fizemos anteriormente:



Salve o script e anexe a imagem na variável "img"do script do GameManager.
Crie agora um Button dentro do Canvas com o Texto "View Image" e o ancore e posicione da forma que achar melhor.
Crie um novo C# script chamado "ButtonScript" e crie uma variável pública do tipo "Image" chamada "img" e uma variável private booleana chamada "isEnabled" iniciando em "false".
Agora defina um método público sem retorno chamado "ClickViewImage" e faça com que a variável "isEnabled" receba seu oposto. Após isso, faça com que a "img" seja ativada se isEnabled for true e desativada se isEnabled for false:



Salve o script e o adicione no botão dentro do Canvas, anexando a imagem desativada dentro da variável "img". Dentro do evento "OnClick" clique no sinal de mais (+)  e em "None" adicione o próprio objeto do botão, e após isso em "No Function" vá em "ButtonScript >> ClickViewImage" para esse método seja disparado quando clicarmos no botão.
Teste o jogo e clique no button: Perceba que quando faz isso, a imagem original do quebra-cabeças aparece no canto da tela, e ao clicar novamente no button, ela é desativada:



Isso pode auxiliar o jogador a ver como a imagem é de fato para poder montar o quebra-cabeças.

Final:
E chegamos (ufa) a mais um final de tutorial por aqui! A sugestão que eu ainda tenho para melhorar o jogo é criar um menu que possibilite o jogador a escolher com qual imagem ele quer jogar(uma gama de opções de imagens, ao invés de uma) e a dificuldade através da escolha da grade.
Esse tutorial foi muito trabalhoso de fazer (acho que foi o mais de todos) portanto, se gostou e se te ajudou em algo (mesmo se não for com relação ao desenvolvimento de um quebra-cabeças, mas ao aprender sobre algum comando que você não sabia) deixe o seu feedback, isso é muito importante para me motivar a continuar postando tutoriais e aulas aqui.
Por fim, caso queira, aqui está o link do código fonte no gitHub: https://github.com/mayleone1994/PuzzleGame

Até mais!

Infelizmente a Unity não roda aqui, então não estou praticando estes. Mas acompanho porque é sempre bom ver os códigos. Aliás, gostei bastante dessa ideia de mostrar os trechos desfocados para focar no que é dito. Ajuda bastante pra pessoas lerdas como eu que se esquecem de onde estavam toda vez que sai da janela de edição pra ver o texto da aula.

E pô, cada coisa legal que dá pra fazer com matrizes. Suas aulas estão me motivando a aprender mais sobre elas.  :wow:

A unity pode ter um personagem em 3d, pra servir de modelo para criar posições em que sera útil em uma visão 2d, tipo suporta um modelo pra tirar fotos em diferentes ângulos?

Quanto ao quebra cabeça achei legal, parece bem detalhado.