[TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
5 participantes
Página 1 de 1
[TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
Olá pessoal! No tutorial de hoje nós vamos desenvolver um jogo de quebra-cabeças (aqueles clássicos de encaixar as peças para formar uma imagem) na engine Unity.
Em nosso tutorial, será possível escolher qualquer imagem de qualquer tamanho para montar o quebra-cabeças, também será possível decidir o nível de dificuldade do mesmo, onde podemos ter quantidade de peças diferentes dependendo da grade selecionada, por exemplo, uma grade 4x4 terá ao todo 16 peças para serem montadas.
Em nosso jogo, teremos uma grade que irá orientar o jogador da onde cada peça deverá se encaixar, então em nosso tutorial iremos programar também a criação dessa grade proporcional à quantidade de peças, iremos criar o sistema de clique com o mouse, verificação de vitória, recorte da imagem e muito mais.
O resultado final será exatamente esse aqui:
Recortando a imagem:
Uma das primeiras coisas que faremos em nosso tutorial será "cortar" em vários pedacinhos a imagem que será usada como quebra-cabeças.
Antes de iniciarmos de fato a programação de tal sistema no Unity, vamos entender como faríamos esse tipo de "recorte", irei usar como exemplo a imagem abaixo:
Essa imagem foi reduzida para se adequar ao tamanho do site corretamente, porém ela possui originalmente as dimensões 900x800 (largura e altura).
Supondo que desejamos cortar essa imagem em exatos 4 pedaços, isso significa que teremos uma grade 2x2 ou seja, teremos 2 pedaços a serem cortados horizontalmente e mais 2 pedaços na vertical, totalizando 4 peças.
Então tudo certo, sabemos a quantidade de peças e a grade (quantas imagens iremos ter nas linhas e colunas), mas a grande pergunta é... Qual o tamanho(dimensão) de cada pedaço, para que sejam cortados com exatidão sem sobrar partes da imagem?
O cálculo é mais simples do que pensa, para saber a largura do pedaço, basta dividir a largura total da imagem pelo valor da grade (no caso aqui, 2) e o mesmo com a altura, divida a altura da imagem pela dimensão da grade (2).
Por fim, iremos concluir que cada pedaço terá 450x400 de dimensão (900/2) e (800/2), isso significa que teremos 4 imagens de 450x400 de dimensão numa grade 2x2, ou seja, 2 imagens para as colunas e 2 para as linhas, dessa forma:
O padrão se repete caso queria ter mais peças.
Por exemplo, no caso de querer 9 peças, teremos uma grade 3x3, ou seja, teremos 3 imagens para as linhas e 3 para as colunas.
Cada pedaço terá 300x266 pixels de altura e largura (caso a imagem possua 900x800 de dimensão).
Dessa forma, podemos então descobrir o tamanho de cada retângulo (parte da imagem/peça) quando formos cortar as peças através de código.
Script para recortar as peças:
Agora que está entendido como iremos "cortar" a imagem, abra um novo projeto 2D na engine com o nome "Puzzle Game" e importe a imagem que desejá cortar, também crie um prefab de um sprite 2D com o nome de "Piece", a única coisa que ele terá por enquanto será um box collider 2D.
Esse prefab será responsável por "ser a peça", onde exibirá seu sprite como sendo uma parte da imagem.
Para cortar uma imagem devemos utilizar uma Texture2D para representá-la, porém, para poder manipular as textures importadas do projeto devemos permitir que elas sejam passíveis à leitura e escrita, portanto, clique na imagem importada e através do inspector vá à opção "Texture Type >> Advanced" e marque a caixa "Read/Write Enabled" e dê apply:
Dessa forma poderemos ter acesso à textura via código.
Agora crie um script chamado "CropTextures" para que possamos recortar as imagens.
Vamos agora criar algumas variáveis públicas para esse script: Um enumerador que irá definir o tamanho da grade do jogo, variando entre as grades 2x2 até 9x9 (você pode colocar mais, se desejar), uma variável do tipo do nosso enumerador para receber a resolução da grade, uma variável do tipo Texture2D chamada "sourceTexture" para recebermos a imagem que será cortada e uma variável do tipo "GameObject" para armazenar o prefab da peça a ser instanciada.
Também criaremos duas variáveis privadas, uma do tipo "int" chamada "amoutPieces" que representará a quantidade de peças e um Vector2 chamado "resolutionPieces" para armazenar a resolução de cada parte da imagem (aquele retângulo que vimos no exemplo anterior):
Vamos agora criar um método chamado "StartComponents" para inicializarmos alguns valores.
Por enquanto os únicos valores a serem iniciados serão a quantidade de peças que deve representar o índice do enum "Options", e a resolução das peças que deve ser calculada de acordo com o cálculo que eu mostrei a vocês, ou seja, resolutionPieces.x deve ser a largura da texture dividida pela grade na horizontal, e resolutionPieces.y deve ser a altura da texture dividida pela grade na vertical:
Chame esse método dentro do Start do script.
Agora vamos criar o método que de fato cortará a textura em vários pedaços, para criá-lo deixe-o retornar uma Texture2D, chame-o de "CropTexture" e defina dois parâmetros do tipo inteiro, um chamado "row" para representar a coluna que ele deve cortar a imagem e outro chamado "line" para representar a linha.
Agora crie duas variáveis locais (chamadas resolutionX e resolutionY) para que elas recebam os valores da resolução das peças arredondadas (utilize o Mathf.RoundToInt para isso):
Então agora utilizaremos uma função da classe "Texture2D" que se chama "GetPixels".
Essa função irá retornar os pixels de uma textura/imagem de acordo com a posição e tamanho que serão passados como argumento em formato de um retângulo, deverá ser passado a posição na imagem onde os pixels serão obtidos e a resolução dessa área. A função retorna os pixels da área desejada em forma de um vetor do tipo "Color".
Vamos então criar uma variável local chamada "pixels" do tipo array Color e fazer com que receba a função "GetPixels" sendo chamada através da textura que será anexada ao inspector que ficará armazenada na variável "sourceTexture":
Agora devemos passar o retângulo (área que deve-se obter os pixels da imagem) como argumento da função.
Esse argumento é um retângulo como já dito, que vai pedir a posição X da onde ele vai obter os pixels e a posição Y, então multiplique o valor da coluna (row) pela resolução em X da peça (row*resolutionX) e multiplique a linha pela resolução em Y (line*resolutionY).
Para saber o tamanho da área a ser obtida, basta passar o valor da resolução em X e em Y, respetivamente:
Como iremos criar um laço de repetição que irá percorrer a imagem completa de acordo com as dimensões da mesma, a função "CropTexture" irá ser chamada toda vez que uma parte da imagem for percorrida, representada pelos parâmetros "row" e "line" para sabermos em qual coluna e linha o laço está, dessa forma, a nossa função irá retornar um pedaço da textura num local específico da imagem toda vez que for chamada.
Agora vamos criar uma nova Texture2D para ser retornada da função com a parte da imagem que foi recortada através da função "GetPixels"
Para isso, basta criar uma variável local chamada "tex" e dentro do seu construtor passar a resolução dessa nova textura a ser criada, no caso, resolutionX e resolutionY.
Após fazer isso, preencha essa nova textura com os pixels obtidos anteriormente e que estão armazenados no array "pixels", através da função "SetPixels". Aplique essa atualização através da função "Apply" e retorne da função a variável "tex":
Criando as posições para as peças:
Nessa altura do tutorial nós temos como retornar cada parte da imagem de acordo com sua resolução, mas ainda não temos onde colocar essas imagens das peças.
Nós iremos representar a imagem de cada peça a partir daquele prefab já citado anteriormente que é apenas um sprite2D com um collider.
Antes de instanciarmos o prefab com a imagem cortada em seu sprite, vamos definir as possíveis posições onde esses prefabs serão sorteados.
Para que o jogo fique mais interesse, vamos sortear os pedacinhos em locais aleatórios da cena e toda vez que o jogo ser iniciado novamente, os pedacinhos irão trocar de posições.
Vamos criar mais algumas variáveis do tipo "Vector2": uma chamada "position" que irá guardar a posição da peça que será instanciada, outra chamada "distancePieces" que irá guardar a resolução do tamanho das peças para que possamos instanciá-las uma do lado da outra, e duas listas do tipo "Vector2" uma chamada "positions" para guardar todas as posições possíveis que as peças ficarão, e outra chamada "sortedPieces" que irá armazenadas as peças já sorteadas, para que não se repita um local e alguma peça fique sobre a outra:
Criaremos agora um método sem retorno chamado "CreatePositions" que vai ser responsável por de fato, criar as possíveis posições para que as peças possam ser instanciadas.
Vamos calcular primeiramente a distância de cada peça, que deve ser em "x" a resolução da peça em X divida por 100 (se a resolução da peça for 300, por exemplo, a distância entre essas peças na Unity será de 3 pixels, já que estamos utilizando 100 pixels per unit para o tamanho da imagem) e para Y será o mesmo princípio.
Depois disso, crie um laço de repetição que tem como contador um inteiro chamado "x" e que vai ser incrementado até que o mesmo seja menor que a quantidade de peças, e dentro desse laço defina outro laço com o contador chamado "y" que irá ser incrementado até que seu valor seja menor que a quantidade de peças.
Agora adicione dentro da lista "positions" um novo Vector2 em x tendo a distância das peças multiplicado pelo contador "x" e em "y" a distância das peças multiplicado pelo contador "y":
Então se por exemplo temos uma peça com resolução de 400 pixels(de largura e altura) e uma grade 2x2, as posições serão:
0 e 0,
0 e 4,
4 e 0,
4 e 4.
Ou seja, teremos duas peças nas posições 0;0 e 0;4 (primeira linha) e duas peças nas posições 4;0 e 4;4 (segunda linha), ou seja, uma grade 2x2 com duas linhas e duas colunas.
A distância entre cada peça é de 4 pixels tanto em x quanto em y, pois dividimos a resolução da peça por 100, fazendo com que quando as peças forem instanciadas em alguma dessas quatro posições, elas fiquem exatamente uma do lado da outra, formando então uma grade 2x2 com 4 peças.
Chame esse método "CreatePositions" dentro do Start do script.
Randomizando posições:
Agora que temos dentro da lista "positions" todas as possíveis posições para as peças, vamos randomizá-las, sorteando aleatoriamente sempre uma nova posição para a peça que está sendo instanciada.
Para tal, crie um método chamado "RandomPosition" que retorna um Vector2.
Dentro desse método crie uma variável local chamada "sorted" para definirmos se alguma posição válida foi sorteada, então inicie essa variável como sendo "false".
Crie uma outra variável local chamada "pos" que irá conter as posições sorteadas e a inicialize como um Vector zero.
Agora crie um laço "while" que irá ficar sendo executado enquanto "sorted" for false.
Dentro do laço faça com que a variável "pos" receba um valor aleatório dentro da lista "positions" através do método Random.Range.
Após isso, nós iremos armazenar dentro da variável "sorted" se essa posição sorteada aleatoriamente já foi sorteada antes, para isso, basta verificar se dentro da lista "sortedPositions" o valor de "pos" não está contido nela.
Caso "sorted" seja true (ou seja, o valor ainda não foi sorteado) armazene dentro da variável "sortedPositions" o valor de "pos", já que é uma posição que ainda não foi sorteada. Após o laço, apenas retorne "pos":
Pronto! Agora temos um método que irá randomizar as posições das peças de acordo com as possíveis posições na cena.
Instanciando as peças:
Vamos então finalmente instanciar as peças na cena que irão representar as partes da textura recortada.
Crie um método sem retorno com o nome "CreatePiece" e o chame dentro do Start do script.
Agora vamos instanciar os prefabs de acordo com a quantidade de peças.
Para isso, vamos criar dois laços de repetições aninhados (igual fizemos com o CreatePositions), assim, nós iremos percorrer as linhas e colunas da textura como um todo.
Vamos representar o primeiro laço pela obtenção das linhas da imagem(esse contador será chamado de 'i') e o segundo laço, as colunas da imagem (contador com o nome de 'j'), para representarmos uma matriz ixj.
Se começarmos o laço 'i' do zero, ele vai começar da última linha da imagem, ou seja, a primeira parte da imagem vai ser obtida a partir da última linha da mesma, então se quisermos que a primeira parte da imagem seja obtida a partir da sua primeira linha, devemos iniciar esse laço de trás pra frente, assim a imagem será recortada de cima para baixo da esquerda para a direita.
Então crie uma variável local chamada "start" e nela armazene "amoutPieces" -1.
Agora crie o laço "i" iniciando de start, e tendo seu delimitador até que "i" seja maior ou igual a 0, recebendo um decremento de um em um.
Já dentro do laço 'i' com o laço 'j', sua definição é mais simples: basta o iniciar a partir do 0, e se delimitar até que seu valor seja menor que "amoutPieces", recebendo o incremento de um em um:
Vamos finalmente criar o prefab contendo a parte da textura cortada, crie uma variável local chamada "texture" e a faça receber a chamada da nossa função "CropTexture", passando como argumento j e i, respectivamente.
Faça com que a variável "position" receba a chamada da nossa função "RandomPosition" para que seja sorteada uma posição para esse prefab, e por fim, faça com que uma variável local chamada "quad" receba a instância do prefab "piecePrefab", na posição "position".
Agora faremos com que esse prefab tenha como imagem (sprite) o pedaço da peça cortada que está armazenada na variável "texture":
Através da variável "quad" pegue o componente "SpriteRenderer" do objeto e acesse sua propriedade "sprite". Através do comando "Sprite.Create" crie um novo sprite para esse prefab, passando como argumento a textura que irá ser criada (no caso, a variável "texture" que contém a parte cortada da imagem), e um novo retângulo que irá definir a dimensão desse sprite, ou seja, 0 e 0 para a imagem ser obtida a partir do canto inferior esquerdo, com as dimensões de "texture" para a largura e altura, e após definir o retângulo, defina o pivot dessa imagem, recomendo que seja central, sendo um novo Vector2 com os valores 0.5 e 0.5 (centro da imagem):
Vamos então ajustar o colisor do sprite para que fique do tamanho da textura cortada, para isso, através da variável "quad" obtenha o componente "BoxCollider2D"e faça acesso à propriedade "size", dentro dela armazene um novo Vector2 com "X" sendo o valor de "distancePieces.x" e "Y" sendo "distancePieces.y":
Salve o script.
Agora crie um novo objeto vazio em cena com o nome de "GameManager" e nele adicione o script "CropTexture".
Através do inspector selecione um tamanho qualquer para a grade, anexe a imagem que deseja ser recortada para o quebra-cabeças e o prefab da peça:
Teste o jogo e em tempo de execução ajuste a posição e campo de visão da câmera.
Perceba que as peças são instanciadas de acordo com a grade selecionada, em diversos pedaços, um do lado do outro:
Exemplo de grade 4x4 com 16 peças.
Exemplo de grade 2x2 com 4 peças.
Criando a grade:
Com a instanciação das peças, vamos então criar a instância da grade, para que cada parte da imagem seja encaixada corretamente.
Para tal, crie através de um editor de imagens de sua preferência um quadrado com a resolução 150x150(por exemplo), transparente e com uma borda interna cinza com 2 pixels de espessura. Salve a imagem em formato .png com o nome de "quad", a imagem será mais ou menos assim:
Importe a imagem para o projeto.
Agora crie um novo Sprite na cena com o nome de "Grid", contendo apenas um BoxCollider2D com a opção "Is Trigger" marcada, tendo como "sprite" a imagem que acabara de importar. Faça com que esse objeto se torne um prefab.
Vamos então voltar para o script "CropTexture" e criar a variável "gridPrefab" como pública:
Após ter feito isso, crie um método sem retorno chamado "CreateGrid" que vai receber três parâmetros: dois inteiros chamados "j" e "i" e um GameObject chamado "quad".
Crie uma variável local chamada "grid" e a faça receber o Instantiate contendo o prefab "gridPrefab", um novo Vector2 recebendo em "x" a variável 'j' multiplicando a distância das peças em X e subtraindo esse valor por 10 (fazendo com que a grade seja instanciada com 10 pixels de distância das peças), e em "y" a variável 'i' multiplicando a distância das peças em Y.
Vamos também criar uma variável local chamada "newScale" para determinar a nova escala desse sprite de acordo com a grade das peças, fazendo com que essa variável receba um novo Vector2 em "x" sendo a resolução da peça em x dividida pela largura do sprite "quad" (que no caso é 150) e em "y" a resolução da peça em y também dividida por 150.
Agora é só aplicar "newScale" em x e y para "localScale" de "grid":
Agora chame o método "CreateGrid" dentro do método "CreatePiece" após criar a peça passando como argumento as variáveis "j" e "i" e também "quad":
Salve o script e através do inspector anexe o prefab "grid" na variável "gridPrefab" no GameManager. Teste o jogo:
Exemplo de uma grade criada a partir de 9 peças (grade 3x3).
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:
Desativa 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!
Re: [TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
Que tutorial topppp.
@MayLeone, você já pensou em vender cursos na Udemy? A qualidade dos seus tutoriais é bem alta, e o principal, é bastaante detalhado.
Meio que direto aparece gente pedindo se eu tenho cursos pra vender na Udemy, ou seja, tem público. Qualidade alta em tutoriais pode ser bem explorada em sites de vendas de tutoriais, e gera um money extra significativo as vezes.
@MayLeone, você já pensou em vender cursos na Udemy? A qualidade dos seus tutoriais é bem alta, e o principal, é bastaante detalhado.
Meio que direto aparece gente pedindo se eu tenho cursos pra vender na Udemy, ou seja, tem público. Qualidade alta em tutoriais pode ser bem explorada em sites de vendas de tutoriais, e gera um money extra significativo as vezes.
Re: [TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
Vlw Marcos!
Eu realmente queria começar um curso de criação de jogos para iniciantes com Unity lá na Udemy, massssss me falta o essencial, um equipamento descente (máquina boa + microfone), sem dizer que minha casa é bem barulhenta, então quase sempre eu me estresso gravado vídeos, por isso prefiro escrever, apesar de que dá muito mais trabalho.
Mas eu pretendo normalizar essa situação e levar a sério a ideia desses cursos e etc.
Obrigada pelo feeback, é bem importante pra eu saber se estou no caminho certo.
Vlw vc também, dstaroski!
Eu realmente queria começar um curso de criação de jogos para iniciantes com Unity lá na Udemy, massssss me falta o essencial, um equipamento descente (máquina boa + microfone), sem dizer que minha casa é bem barulhenta, então quase sempre eu me estresso gravado vídeos, por isso prefiro escrever, apesar de que dá muito mais trabalho.
Mas eu pretendo normalizar essa situação e levar a sério a ideia desses cursos e etc.
Obrigada pelo feeback, é bem importante pra eu saber se estou no caminho certo.
Vlw vc também, dstaroski!
Re: [TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
MayLeone escreveu:Vlw Marcos!
Eu realmente queria começar um curso de criação de jogos para iniciantes com Unity lá na Udemy, massssss me falta o essencial, um equipamento descente (máquina boa + microfone), sem dizer que minha casa é bem barulhenta, então quase sempre eu me estresso gravado vídeos, por isso prefiro escrever, apesar de que dá muito mais trabalho.
Mas eu pretendo normalizar essa situação e levar a sério a ideia desses cursos e etc.
Obrigada pelo feeback, é bem importante pra eu saber se estou no caminho certo.
Vlw vc também, dstaroski!
Ou MayLeone, você escreve bem e de uma forma bastante didática. Existem poucos livros em português da Unity ou talvez nenhum. Ainda existem alguns cursos regulares, técnicos e superiores que estão demandando esse tipo de conteúdo.
O que acha de começar um projeto de um livro? Eu acho que vc teria um bom conteúdo final,l viu.
fabricadegame- Membro
- PONTOS : 2213
REPUTAÇÃO : 11
Respeito as regras :
Re: [TUTORIAL] Desenvolvendo um jogo de quebra-cabeças
Muito bom MayLeone, eu estava procurando algo do tipo esses dias em vídeo e não encontrei nada, ao menos bem explicado não, e geralmente quando encontro é em inglês e as vezes dificulta um pouco, realmente seria uma boa pra você o que o Marcos disse logo acima, criar um curso e mandar pra udemy, talento e conhecimento para criar um conteúdo de qualidade você tem, bom é isso agradeço pelo tutorial já me deu mais uma ideia kkkk e sucesso aí, vlw!!!MayLeone escreveu:
Olá pessoal! No tutorial de hoje nós vamos desenvolver um jogo de quebra-cabeças (aqueles clássicos de encaixar as peças para formar uma imagem) na engine Unity.
Em nosso tutorial, será possível escolher qualquer imagem de qualquer tamanho para montar o quebra-cabeças, também será possível decidir o nível de dificuldade do mesmo, onde podemos ter quantidade de peças diferentes dependendo da grade selecionada, por exemplo, uma grade 4x4 terá ao todo 16 peças para serem montadas.
Em nosso jogo, teremos uma grade que irá orientar o jogador da onde cada peça deverá se encaixar, então em nosso tutorial iremos programar também a criação dessa grade proporcional à quantidade de peças, iremos criar o sistema de clique com o mouse, verificação de vitória, recorte da imagem e muito mais.
O resultado final será exatamente esse aqui:
Recortando a imagem:
Uma das primeiras coisas que faremos em nosso tutorial será "cortar" em vários pedacinhos a imagem que será usada como quebra-cabeças.
Antes de iniciarmos de fato a programação de tal sistema no Unity, vamos entender como faríamos esse tipo de "recorte", irei usar como exemplo a imagem abaixo:
Essa imagem foi reduzida para se adequar ao tamanho do site corretamente, porém ela possui originalmente as dimensões 900x800 (largura e altura).
Supondo que desejamos cortar essa imagem em exatos 4 pedaços, isso significa que teremos uma grade 2x2 ou seja, teremos 2 pedaços a serem cortados horizontalmente e mais 2 pedaços na vertical, totalizando 4 peças.
Então tudo certo, sabemos a quantidade de peças e a grade (quantas imagens iremos ter nas linhas e colunas), mas a grande pergunta é... Qual o tamanho(dimensão) de cada pedaço, para que sejam cortados com exatidão sem sobrar partes da imagem?
O cálculo é mais simples do que pensa, para saber a largura do pedaço, basta dividir a largura total da imagem pelo valor da grade (no caso aqui, 2) e o mesmo com a altura, divida a altura da imagem pela dimensão da grade (2).
Por fim, iremos concluir que cada pedaço terá 450x400 de dimensão (900/2) e (800/2), isso significa que teremos 4 imagens de 450x400 de dimensão numa grade 2x2, ou seja, 2 imagens para as colunas e 2 para as linhas, dessa forma:
O padrão se repete caso queria ter mais peças.
Por exemplo, no caso de querer 9 peças, teremos uma grade 3x3, ou seja, teremos 3 imagens para as linhas e 3 para as colunas.
Cada pedaço terá 300x266 pixels de altura e largura (caso a imagem possua 900x800 de dimensão).
Dessa forma, podemos então descobrir o tamanho de cada retângulo (parte da imagem/peça) quando formos cortar as peças através de código.
Script para recortar as peças:
Agora que está entendido como iremos "cortar" a imagem, abra um novo projeto 2D na engine com o nome "Puzzle Game" e importe a imagem que desejá cortar, também crie um prefab de um sprite 2D com o nome de "Piece", a única coisa que ele terá por enquanto será um box collider 2D.
Esse prefab será responsável por "ser a peça", onde exibirá seu sprite como sendo uma parte da imagem.
Para cortar uma imagem devemos utilizar uma Texture2D para representá-la, porém, para poder manipular as textures importadas do projeto devemos permitir que elas sejam passíveis à leitura e escrita, portanto, clique na imagem importada e através do inspector vá à opção "Texture Type >> Advanced" e marque a caixa "Read/Write Enabled" e dê apply:
Dessa forma poderemos ter acesso à textura via código.
Agora crie um script chamado "CropTextures" para que possamos recortar as imagens.
Vamos agora criar algumas variáveis públicas para esse script: Um enumerador que irá definir o tamanho da grade do jogo, variando entre as grades 2x2 até 9x9 (você pode colocar mais, se desejar), uma variável do tipo do nosso enumerador para receber a resolução da grade, uma variável do tipo Texture2D chamada "sourceTexture" para recebermos a imagem que será cortada e uma variável do tipo "GameObject" para armazenar o prefab da peça a ser instanciada.
Também criaremos duas variáveis privadas, uma do tipo "int" chamada "amoutPieces" que representará a quantidade de peças e um Vector2 chamado "resolutionPieces" para armazenar a resolução de cada parte da imagem (aquele retângulo que vimos no exemplo anterior):
Vamos agora criar um método chamado "StartComponents" para inicializarmos alguns valores.
Por enquanto os únicos valores a serem iniciados serão a quantidade de peças que deve representar o índice do enum "Options", e a resolução das peças que deve ser calculada de acordo com o cálculo que eu mostrei a vocês, ou seja, resolutionPieces.x deve ser a largura da texture dividida pela grade na horizontal, e resolutionPieces.y deve ser a altura da texture dividida pela grade na vertical:
Chame esse método dentro do Start do script.
Agora vamos criar o método que de fato cortará a textura em vários pedaços, para criá-lo deixe-o retornar uma Texture2D, chame-o de "CropTexture" e defina dois parâmetros do tipo inteiro, um chamado "row" para representar a coluna que ele deve cortar a imagem e outro chamado "line" para representar a linha.
Agora crie duas variáveis locais (chamadas resolutionX e resolutionY) para que elas recebam os valores da resolução das peças arredondadas (utilize o Mathf.RoundToInt para isso):
Então agora utilizaremos uma função da classe "Texture2D" que se chama "GetPixels".
Essa função irá retornar os pixels de uma textura/imagem de acordo com a posição e tamanho que serão passados como argumento em formato de um retângulo, deverá ser passado a posição na imagem onde os pixels serão obtidos e a resolução dessa área. A função retorna os pixels da área desejada em forma de um vetor do tipo "Color".
Vamos então criar uma variável local chamada "pixels" do tipo array Color e fazer com que receba a função "GetPixels" sendo chamada através da textura que será anexada ao inspector que ficará armazenada na variável "sourceTexture":
Agora devemos passar o retângulo (área que deve-se obter os pixels da imagem) como argumento da função.
Esse argumento é um retângulo como já dito, que vai pedir a posição X da onde ele vai obter os pixels e a posição Y, então multiplique o valor da coluna (row) pela resolução em X da peça (row*resolutionX) e multiplique a linha pela resolução em Y (line*resolutionY).
Para saber o tamanho da área a ser obtida, basta passar o valor da resolução em X e em Y, respetivamente:
Como iremos criar um laço de repetição que irá percorrer a imagem completa de acordo com as dimensões da mesma, a função "CropTexture" irá ser chamada toda vez que uma parte da imagem for percorrida, representada pelos parâmetros "row" e "line" para sabermos em qual coluna e linha o laço está, dessa forma, a nossa função irá retornar um pedaço da textura num local específico da imagem toda vez que for chamada.
Agora vamos criar uma nova Texture2D para ser retornada da função com a parte da imagem que foi recortada através da função "GetPixels"
Para isso, basta criar uma variável local chamada "tex" e dentro do seu construtor passar a resolução dessa nova textura a ser criada, no caso, resolutionX e resolutionY.
Após fazer isso, preencha essa nova textura com os pixels obtidos anteriormente e que estão armazenados no array "pixels", através da função "SetPixels". Aplique essa atualização através da função "Apply" e retorne da função a variável "tex":
Criando as posições para as peças:
Nessa altura do tutorial nós temos como retornar cada parte da imagem de acordo com sua resolução, mas ainda não temos onde colocar essas imagens das peças.
Nós iremos representar a imagem de cada peça a partir daquele prefab já citado anteriormente que é apenas um sprite2D com um collider.
Antes de instanciarmos o prefab com a imagem cortada em seu sprite, vamos definir as possíveis posições onde esses prefabs serão sorteados.
Para que o jogo fique mais interesse, vamos sortear os pedacinhos em locais aleatórios da cena e toda vez que o jogo ser iniciado novamente, os pedacinhos irão trocar de posições.
Vamos criar mais algumas variáveis do tipo "Vector2": uma chamada "position" que irá guardar a posição da peça que será instanciada, outra chamada "distancePieces" que irá guardar a resolução do tamanho das peças para que possamos instanciá-las uma do lado da outra, e duas listas do tipo "Vector2" uma chamada "positions" para guardar todas as posições possíveis que as peças ficarão, e outra chamada "sortedPieces" que irá armazenadas as peças já sorteadas, para que não se repita um local e alguma peça fique sobre a outra:
Criaremos agora um método sem retorno chamado "CreatePositions" que vai ser responsável por de fato, criar as possíveis posições para que as peças possam ser instanciadas.
Vamos calcular primeiramente a distância de cada peça, que deve ser em "x" a resolução da peça em X divida por 100 (se a resolução da peça for 300, por exemplo, a distância entre essas peças na Unity será de 3 pixels, já que estamos utilizando 100 pixels per unit para o tamanho da imagem) e para Y será o mesmo princípio.
Depois disso, crie um laço de repetição que tem como contador um inteiro chamado "x" e que vai ser incrementado até que o mesmo seja menor que a quantidade de peças, e dentro desse laço defina outro laço com o contador chamado "y" que irá ser incrementado até que seu valor seja menor que a quantidade de peças.
Agora adicione dentro da lista "positions" um novo Vector2 em x tendo a distância das peças multiplicado pelo contador "x" e em "y" a distância das peças multiplicado pelo contador "y":
Então se por exemplo temos uma peça com resolução de 400 pixels(de largura e altura) e uma grade 2x2, as posições serão:
0 e 0,
0 e 4,
4 e 0,
4 e 4.
Ou seja, teremos duas peças nas posições 0;0 e 0;4 (primeira linha) e duas peças nas posições 4;0 e 4;4 (segunda linha), ou seja, uma grade 2x2 com duas linhas e duas colunas.
A distância entre cada peça é de 4 pixels tanto em x quanto em y, pois dividimos a resolução da peça por 100, fazendo com que quando as peças forem instanciadas em alguma dessas quatro posições, elas fiquem exatamente uma do lado da outra, formando então uma grade 2x2 com 4 peças.
Chame esse método "CreatePositions" dentro do Start do script.
Randomizando posições:
Agora que temos dentro da lista "positions" todas as possíveis posições para as peças, vamos randomizá-las, sorteando aleatoriamente sempre uma nova posição para a peça que está sendo instanciada.
Para tal, crie um método chamado "RandomPosition" que retorna um Vector2.
Dentro desse método crie uma variável local chamada "sorted" para definirmos se alguma posição válida foi sorteada, então inicie essa variável como sendo "false".
Crie uma outra variável local chamada "pos" que irá conter as posições sorteadas e a inicialize como um Vector zero.
Agora crie um laço "while" que irá ficar sendo executado enquanto "sorted" for false.
Dentro do laço faça com que a variável "pos" receba um valor aleatório dentro da lista "positions" através do método Random.Range.
Após isso, nós iremos armazenar dentro da variável "sorted" se essa posição sorteada aleatoriamente já foi sorteada antes, para isso, basta verificar se dentro da lista "sortedPositions" o valor de "pos" não está contido nela.
Caso "sorted" seja true (ou seja, o valor ainda não foi sorteado) armazene dentro da variável "sortedPositions" o valor de "pos", já que é uma posição que ainda não foi sorteada. Após o laço, apenas retorne "pos":
Pronto! Agora temos um método que irá randomizar as posições das peças de acordo com as possíveis posições na cena.
Instanciando as peças:
Vamos então finalmente instanciar as peças na cena que irão representar as partes da textura recortada.
Crie um método sem retorno com o nome "CreatePiece" e o chame dentro do Start do script.
Agora vamos instanciar os prefabs de acordo com a quantidade de peças.
Para isso, vamos criar dois laços de repetições aninhados (igual fizemos com o CreatePositions), assim, nós iremos percorrer as linhas e colunas da textura como um todo.
Vamos representar o primeiro laço pela obtenção das linhas da imagem(esse contador será chamado de 'i') e o segundo laço, as colunas da imagem (contador com o nome de 'j'), para representarmos uma matriz ixj.
Se começarmos o laço 'i' do zero, ele vai começar da última linha da imagem, ou seja, a primeira parte da imagem vai ser obtida a partir da última linha da mesma, então se quisermos que a primeira parte da imagem seja obtida a partir da sua primeira linha, devemos iniciar esse laço de trás pra frente, assim a imagem será recortada de cima para baixo da esquerda para a direita.
Então crie uma variável local chamada "start" e nela armazene "amoutPieces" -1.
Agora crie o laço "i" iniciando de start, e tendo seu delimitador até que "i" seja maior ou igual a 0, recebendo um decremento de um em um.
Já dentro do laço 'i' com o laço 'j', sua definição é mais simples: basta o iniciar a partir do 0, e se delimitar até que seu valor seja menor que "amoutPieces", recebendo o incremento de um em um:
Vamos finalmente criar o prefab contendo a parte da textura cortada, crie uma variável local chamada "texture" e a faça receber a chamada da nossa função "CropTexture", passando como argumento j e i, respectivamente.
Faça com que a variável "position" receba a chamada da nossa função "RandomPosition" para que seja sorteada uma posição para esse prefab, e por fim, faça com que uma variável local chamada "quad" receba a instância do prefab "piecePrefab", na posição "position".
Agora faremos com que esse prefab tenha como imagem (sprite) o pedaço da peça cortada que está armazenada na variável "texture":
Através da variável "quad" pegue o componente "SpriteRenderer" do objeto e acesse sua propriedade "sprite". Através do comando "Sprite.Create" crie um novo sprite para esse prefab, passando como argumento a textura que irá ser criada (no caso, a variável "texture" que contém a parte cortada da imagem), e um novo retângulo que irá definir a dimensão desse sprite, ou seja, 0 e 0 para a imagem ser obtida a partir do canto inferior esquerdo, com as dimensões de "texture" para a largura e altura, e após definir o retângulo, defina o pivot dessa imagem, recomendo que seja central, sendo um novo Vector2 com os valores 0.5 e 0.5 (centro da imagem):
Vamos então ajustar o colisor do sprite para que fique do tamanho da textura cortada, para isso, através da variável "quad" obtenha o componente "BoxCollider2D"e faça acesso à propriedade "size", dentro dela armazene um novo Vector2 com "X" sendo o valor de "distancePieces.x" e "Y" sendo "distancePieces.y":
Salve o script.
Agora crie um novo objeto vazio em cena com o nome de "GameManager" e nele adicione o script "CropTexture".
Através do inspector selecione um tamanho qualquer para a grade, anexe a imagem que deseja ser recortada para o quebra-cabeças e o prefab da peça:
Teste o jogo e em tempo de execução ajuste a posição e campo de visão da câmera.
Perceba que as peças são instanciadas de acordo com a grade selecionada, em diversos pedaços, um do lado do outro:Exemplo de grade 4x4 com 16 peças.Exemplo de grade 2x2 com 4 peças.
Criando a grade:
Com a instanciação das peças, vamos então criar a instância da grade, para que cada parte da imagem seja encaixada corretamente.
Para tal, crie através de um editor de imagens de sua preferência um quadrado com a resolução 150x150(por exemplo), transparente e com uma borda interna cinza com 2 pixels de espessura. Salve a imagem em formato .png com o nome de "quad", a imagem será mais ou menos assim:Importe a imagem para o projeto.
Agora crie um novo Sprite na cena com o nome de "Grid", contendo apenas um BoxCollider2D com a opção "Is Trigger" marcada, tendo como "sprite" a imagem que acabara de importar. Faça com que esse objeto se torne um prefab.
Vamos então voltar para o script "CropTexture" e criar a variável "gridPrefab" como pública:
Após ter feito isso, crie um método sem retorno chamado "CreateGrid" que vai receber três parâmetros: dois inteiros chamados "j" e "i" e um GameObject chamado "quad".
Crie uma variável local chamada "grid" e a faça receber o Instantiate contendo o prefab "gridPrefab", um novo Vector2 recebendo em "x" a variável 'j' multiplicando a distância das peças em X e subtraindo esse valor por 10 (fazendo com que a grade seja instanciada com 10 pixels de distância das peças), e em "y" a variável 'i' multiplicando a distância das peças em Y.
Vamos também criar uma variável local chamada "newScale" para determinar a nova escala desse sprite de acordo com a grade das peças, fazendo com que essa variável receba um novo Vector2 em "x" sendo a resolução da peça em x dividida pela largura do sprite "quad" (que no caso é 150) e em "y" a resolução da peça em y também dividida por 150.
Agora é só aplicar "newScale" em x e y para "localScale" de "grid":
Agora chame o método "CreateGrid" dentro do método "CreatePiece" após criar a peça passando como argumento as variáveis "j" e "i" e também "quad":
Salve o script e através do inspector anexe o prefab "grid" na variável "gridPrefab" no GameManager. Teste o jogo:
Exemplo de uma grade criada a partir de 9 peças (grade 3x3).
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:
Desativa 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!
Tópicos semelhantes
» Como posso fazer um jogo 2d de quebra cabeça?
» Jogo que estou desenvolvendo "Alone"
» [DEV LIVE] Desenvolvendo Meu Jogo
» Desenvolvendo um jogo estilo Resident Evil
» ESSE EO MEU JOGO A FLORESTA QUE EU ESTOU DESENVOLVENDO
» Jogo que estou desenvolvendo "Alone"
» [DEV LIVE] Desenvolvendo Meu Jogo
» Desenvolvendo um jogo estilo Resident Evil
» ESSE EO MEU JOGO A FLORESTA QUE EU ESTOU DESENVOLVENDO
Página 1 de 1
Permissões neste sub-fórum
Não podes responder a tópicos