Desenvolvimento Unity

Aprenda a desenvolver um jogo de ciclos de luz estilo Tron na Unity 2D

Prefácio

Vamos fazer um jogo 2D estilo Tron muito simples no Unity com menos de 60 linhas de código e apenas três ativos. Dois jogadores poderão competir entre si.

O objetivo é mover sua moto de forma que prenda o outro jogador, como um jogo multiplayer de cobra.

Como sempre, tudo será explicado da forma mais fácil possível para que todos possam entender.

Aqui está uma prévia da jogabilidade final:

Requisitos

Conhecimento

Este tutorial não requer nenhuma habilidade especial, exceto algum conhecimento sobre os fundamentos do Unity, mesmo que você ainda não conheça esses conceitos, porém recomendamos que você aprimore seu conhecimento dos assuntos mencionados antes de tentar este tutorial, veja no canal DFILITTO.

Versão da unidade

Nosso tutorial Tron Light-Cycles usará o Unity 2018.4 , também conhecido como versão 2018 “Long Term Support” (LTS). As versões mais recentes também devem funcionar bem, exceto por conter algumas alterações na interface do usuário, e as versões mais antigas podem ou não funcionar.

Configuração do projeto

Observação: se você não estiver usando o Unity Hub, esse processo será um pouco diferente. Por favor, adapte-se de acordo.

Vamos lá. Iniciaremos o Unity Hub e selecionaremos Novo Projeto :

Vamos nomeá-lo como Tron ou TronLightCycles , selecionar o modelo 2D , selecionar qualquer local como C:\GameDev e clicar em Criar Projeto :

Dê ao Unity alguns minutos para inicializar e descompactar. Mesmo nos computadores topo de linha, esse processo demora um pouco – pegue uma xícara de chá/café e um lanche enquanto o Unity faz seu trabalho.

Assim que o Unity Editor carregar, clique na câmera principal na hierarquia . Isso nos permite definir a cor de fundo como preto e ajustar o tamanho como mostrado na imagem a seguir:


Nota: Dependendo da versão do Unity que você está usando, algumas coisas podem ser diferentes. Por favor, adapte-se de acordo.

A imagem de fundo

Um fundo preto simples é bastante chato, então vamos usar nossa ferramenta de desenho preferida para desenhar algum tipo de imagem de grade que possamos usar como plano de fundo:


Nota: clique com o botão direito na imagem, selecione Salvar como… , navegue até o projeto pasta Assets e salve-o em uma nova pasta Sprites .

Vamos selecionar a imagem em nossa Área de Projeto :

E então dê uma olhada no Inspetor onde podemos modificar as configurações de importação :


Nota: um valor de Pixels por unidade de 2 significa que 2 x 2 pixels caberão em uma unidade no mundo do jogo. Usaremos esse valor para todas as nossas texturas, pois o sprite do player terá o tamanho de 2 x 2 pixels posteriormente. As outras configurações são apenas efeitos visuais. Queremos que a imagem pareça perfeitamente nítida e sem qualquer compressão, caso contrário pode parecer estranha.

Agora podemos adicionar a grade ao nosso mundo de jogo simplesmente arrastando-a da Área do Projeto para a Hierarquia :


Nota: também podemos arrastá-la da Área do Projeto para a Cena , mas também temos que reajustar a posição para ( 0, 0, 0) .

Também alteraremos a propriedade Order in Layer da grade para -1 para garantir que ela seja sempre desenhada no plano de fundo posteriormente:


Nota: normalmente criaríamos uma camada de classificação de plano de fundo totalmente nova , mas para um jogo tão simples, usando a propriedade Order in Layer propriedade é suficiente.

Se pressionarmos Play já podemos ver a grade do jogo:

Se sua cena se parece com a acima, até agora tudo bem. Vamos continuar.

As paredes de luz

Os Players deverão deixar uma lightwall por onde passarem, então vamos criar a primeira.

O Lightwall Ciano

Começaremos desenhando uma imagem de 2 x 2 px que consiste apenas na cor ciano:

Obs: clique com o botão direito na imagem, selecione Salvar como… e salve na pasta Assets/Sprites do projeto.

Usaremos as seguintes configurações de importação para isso:

Depois podemos arrastar a imagem da Área do Projeto para a Cena para criar um GameObject a partir dela:

No momento, o Lightwall é apenas uma imagem no mundo do jogo, nada colidiria com ele. Vamos selecionar Adicionar Componente -> Física 2D -> Box Collider 2D no Inspetor para torná-lo parte do mundo da física:

Agora o Lightwall está concluído. Vamos criar um Prefab a partir dele arrastando-o da Hierarquia para uma nova pasta Prefabs em nossa Área de Projeto :

Ter salvo o Lightwall como Prefab significa que podemos carregá-lo no jogo sempre que quisermos. E como ainda não precisamos que ele esteja no jogo, podemos clicar com o botão direito do mouse no GameObject lightwall_cyan na Hierarquia e selecionar Excluir :

O Lightwall Rosa

Vamos repetir o fluxo de trabalho acima para a imagem rosa do Lightwall:

Obs: clique com o botão direito na imagem, selecione Salvar como… e salve na pasta Assets/Sprites do projeto.

Se feito corretamente, devemos agora ter os pré-fabricados Cyan e Pink em nossa pasta Prefabs assim:

Essas são as paredes claras feitas. Agora é hora de adicionar o ator Player.

O jogador

Agora é hora de adicionar o Player. O Player deve ser um quadrado branco simples que pode ser movido pressionando algumas teclas. O Player também arrastará um Lightwall para onde quer que vá em nosso jogo.

Vamos desenhar uma imagem branca de 2 x 2 px para o player:

Obs: clique com o botão direito na imagem, selecione Salvar como… e salve na pasta Assets/Sprites do projeto.

Usaremos as seguintes configurações de importação para isso:

Agora podemos arrastar a imagem da Área do Projeto para a Cena para criar um GameObject a partir dela. Em seguida, iremos renomeá-lo para player_cyan e posicioná-lo no centro direito do nosso jogo em (3, 0, 0) . Enquanto estamos nisso, vamos garantir que a propriedade Order in Layer esteja definida como 1, pois isso garantirá que o player estará sempre em primeiro plano. Depois de fazer isso, selecionaremos Adicionar Componente -> Física 2D -> Box Collider 2D e configuraremos o componente Box Collider 2D para ser um Trigger.

Algumas notas: Para o jogador, normalmente usaríamos uma Camada de Classificação para isso, como mencionado anteriormente. Porém, como teremos apenas 3 elementos em nosso jogo: o plano de fundo, o player e os Lightwalls, vamos mantê-lo simples e usar apenas três valores diferentes de Ordem na Camada .

Também adicionamos um Box Collider 2D agora para economizar algum tempo mais tarde, bem como habilitar o Is Trigger do Box Collider 2D para evitar colisões com o próprio Lightwall do jogador mais tarde. Enquanto tivermos o IsTrigger habilitado, o jogador receberá apenas informações de colisão, sem realmente colidir com nada. Isso fará sentido muito em breve.

Essa foi uma configuração um pouco demorada apenas para o jogador, mas aguente firme – estamos quase terminando.

Física do Jogador

O jogador também deve se movimentar. Um corpo rígido cuida de coisas como gravidade, velocidade e outras forças que fazem as coisas se moverem. Como regra geral, tudo no mundo da física que supostamente se move precisa de um corpo rígido .

Vamos selecionar Add Component -> Physics 2D -> Rigidbody 2D no Inspector e atribuir as seguintes configurações a ele:


Nota: definimos a Escala de Gravidade como 0 porque não precisamos de nenhuma gravidade em nosso jogo. Além disso, habilitamos Freeze Rotation no eixo Z para evitar que o player gire.

Nosso jogador agora faz parte do mundo da física, é simples assim.

Movimento do Jogador

Usaremos Scripting para fazer o jogador se mover. Nosso script será bastante simples por enquanto, tudo o que precisamos fazer é verificar se as teclas de seta foram pressionadas e modificar a propriedade de velocidade do Rigidbody. O Rigidbody cuidará então de todo o movimento sozinho.
Nota: a velocidade é a direção do movimento multiplicada pela velocidade do movimento.

Vamos selecionar Add Component -> New Script , nomeie-o Move :

Depois podemos clicar duas vezes no Script na Área do Projeto para abri-lo:

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

Em primeiro lugar queremos saber se as teclas de movimento foram pressionadas. Agora queremos criar apenas um script de movimento para ambos os jogadores, então vamos tornar as teclas de movimento personalizáveis ​​para que possamos usar as teclas de seta para um jogador e as teclas WSAD para o outro jogador:

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour {
    // Movement keys (customizable in Inspector)
    public KeyCode upKey;
    public KeyCode downKey;
    public KeyCode rightKey;
    public KeyCode leftKey;

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

Se salvarmos o Script e dermos uma olhada no Inspetor , podemos definir as variáveis-chave para as teclas de seta :

Tudo bem, vamos verificar o pressionamento de teclas em nossa função de atualização :

// Update is called once per frame
void Update () {
    // Check for key presses
    if (Input.GetKeyDown(upKey)) {
        // Do stuff...
    }
    else if (Input.GetKeyDown(downKey)) {
        // Do stuff...
    }
    else if (Input.GetKeyDown(rightKey)) {
        // Do stuff...
    }
    else if (Input.GetKeyDown(leftKey)) {
        // Do stuff...
    }
}

Agora, assim que o jogador pressionar qualquer uma dessas teclas, queremos fazer com que o jogador se mova nessa direção. Como mencionado anteriormente, usaremos a propriedade de velocidade do Rigidbody para isso. A velocidade é sempre a direção do movimento multiplicada pela velocidade do movimento . Vamos adicionar uma variável de velocidade de movimento primeiro:

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour {
    // Movement keys (customizable in Inspector)
    public KeyCode upKey;
    public KeyCode downKey;
    public KeyCode rightKey;
    public KeyCode leftKey;

    // Movement Speed
    public float speed = 16;

    ...
}

O resto será muito simples. Tudo o que precisamos fazer é modificar nossa função Update mais uma vez para definir a propriedade de velocidade do Rigidbody:

// Update is called once per frame
void Update () {
    // Check for key presses
    if (Input.GetKeyDown(upKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.up * speed;
    }
    else if (Input.GetKeyDown(downKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.up * speed;
    }
    else if (Input.GetKeyDown(rightKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
    }
    else if (Input.GetKeyDown(leftKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.right * speed;
    }
}

Nota: -Vector2.up significa baixo e -Vector2.right significa esquerda .

Vamos também modificar nossa função Start bem rápido para dar ao jogador uma velocidade inicial:

// Use this for initialization
void Start () {
    // Initial Velocity
    GetComponent<Rigidbody2D>().velocity = Vector2.up * speed;
}

Se pressionarmos Play , agora podemos mover o jogador com as setas do teclado:

Lightwalls do jogador

Queremos adicionar um recurso que crie um Lightwall onde quer que o jogador vá. Tudo o que realmente precisamos fazer é criar um novo Lightwall assim que o jogador virar para uma nova direção, e então sempre escalar o Lightwall ao longo de onde o jogador vai, até que ele se mova para outra direção.

Precisaremos de uma função auxiliar que gere um novo Lightwall. A princípio adicionaremos duas variáveis ​​ao nosso Script. Um deles será o Lightwall Prefab e o outro será a parede que está sendo arrastada pelo jogador:

public class Move : MonoBehaviour {
    // Movement keys (customizable in Inspector)
    public KeyCode upKey;
    public KeyCode downKey;
    public KeyCode rightKey;
    public KeyCode leftKey;

    // Movement Speed
    public float speed = 16;

    // Wall Prefab
    public GameObject wallPrefab;

    // Current Wall
    Collider2D wall;

    ...

Agora podemos usar Instantiate para criar uma função que gera um novo Lightwall na posição atual do jogador:

void spawnWall() {
    // Spawn a new Lightwall
    GameObject g = Instantiate(wallPrefab, transform.position, Quaternion.identity);
    wall = g.GetComponent<Collider2D>();
}

Nota: transform.position é a posição atual do jogador e Quaternion.identity é a rotação padrão. Também salvamos o Collider2D do GameObject em nossa variável wall para acompanhar a parede atual.

Vamos salvar o Script e então arrastar o lightwall_cyan Prefab da Área do Projeto para o slot wallPrefab do Script :

Tudo bem, é hora de usar nossa função auxiliar. Agora modificaremos a função Update do nosso Script para gerar um novo Lightwall após mudar a direção:

// Update is called once per frame
void Update () {
    // Check for key presses
    if (Input.GetKeyDown(upKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.up * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(downKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.up * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(rightKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(leftKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.right * speed;
        spawnWall();
    }
}

Também geraremos um novo Lightwall quando o jogo começar:

// Use this for initialization
void Start () {
    // Initial Velocity
    GetComponent<Rigidbody2D>().velocity = Vector2.up * speed;
    spawnWall();
}

Se salvarmos o Script e pressionarmos Play , poderemos ver como um novo Lightwall está sendo gerado após cada mudança de direção:

Até agora tudo bem.

Neste momento os Lightwalls são apenas pequenos quadrados, ainda temos que escalá-los. Vamos criar uma nova função fitColliderBetween que pega um Collider e dois pontos e depois ajusta o Collider entre esses dois pontos:

void fitColliderBetween(Collider2D co, Vector2 a, Vector2 b) {
    // Calculate the Center Position
    co.transform.position = a + (b - a) * 0.5f;

    // Scale it (horizontally or vertically)
    float dist = Vector2.Distance(a, b);
    if (a.x != b.x)
        co.transform.localScale = new Vector2(dist, 1);
    else
        co.transform.localScale = new Vector2(1, dist);
}

Explicação do código: esta função pode parecer um pouco confusa no início. A maneira óbvia de ajustar um Collider entre dois pontos seria algo como collider.setMinMax() , mas o Unity não permite isso. Em vez disso, simplesmente usaremos a propriedade transform.position para posicioná-lo exatamente entre os dois pontos e, em seguida, usaremos a propriedade transform.localScale para torná-lo bem longo, para que caiba exatamente entre os pontos. A fórmula a + (b – a) * 0,5 também é muito fácil de entender.

Primeiro de tudo, calculamos a direção de a para b usando (b – a) . Então simplesmente adicionamos metade dessa direção ao ponto a , o que resulta no ponto central. Depois descobrimos se a reta deve seguir horizontalmente ou verticalmente comparando as duas coordenadas x . Se forem iguais, a linha segue horizontalmente, caso contrário, verticalmente. Finalmente ajustamos a escala para que a parede tenha dist unidades de comprimento e 1 unidade de largura.

Vamos usar nossa função fitColliderBetween . Queremos sempre ajustar o Collider entre o final do último Collider e a posição atual do jogador. Então, antes de mais nada, teremos que acompanhar o final do último Collider.

Adicionaremos uma variável lastWallEnd ao nosso script:

public class Move : MonoBehaviour {
    // Movement keys (customizable in Inspector)
    public KeyCode upKey;
    public KeyCode downKey;
    public KeyCode rightKey;
    public KeyCode leftKey;

    // Movement Speed
    public float speed = 16;

    // Wall Prefab
    public GameObject wallPrefab;

    // Current Wall
    Collider2D wall;

    // Last Wall's End
    Vector2 lastWallEnd;

    ...

E defina a posição em nossa função spawnWall :

void spawnWall() {
    // Save last wall's position
    lastWallEnd = transform.position;

    // Spawn a new Lightwall
    GameObject g = Instantiate(wallPrefab, transform.position, Quaternion.identity);
    wall = g.GetComponent<Collider2D>();
}

Nota: tecnicamente a posição da última parede deveria ser wall.transform.position , mas usamos a transform.position do jogador aqui. A razão é que ao gerar a primeira parede, ainda não havia a última parede, por isso não pudemos definir a posição lastWallEnd . Em vez disso, sempre o configuramos para a posição atual do jogador antes de gerar a próxima parede, o que acaba sendo praticamente a mesma coisa.

Quase pronto. Agora podemos modificar nossa função Update novamente para sempre ajustar a parede atual entre a posição final da última parede e a posição atual do jogador:

// Update is called once per frame
void Update () {
    // Check for key presses
    if (Input.GetKeyDown(upKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.up * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(downKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.up * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(rightKey)) {
        GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
        spawnWall();
    }
    else if (Input.GetKeyDown(leftKey)) {
        GetComponent<Rigidbody2D>().velocity = -Vector2.right * speed;
        spawnWall();
    }

    fitColliderBetween(wall, lastWallEnd, transform.position);
}

Se salvarmos o Script e pressionarmos Play , poderemos ver como os Lightwalls estão sendo criados atrás do player:

Se olharmos mais de perto, podemos ver como as paredes são um pouco curtas nos cantos:

Existe uma solução muito fácil para o nosso problema. Tudo o que precisamos fazer é voltar à nossa função fitColliderBetween e sempre deixar a parede uma unidade mais longa:

void fitColliderBetween(Collider2D co, Vector2 a, Vector2 b) {
    // Calculate the Center Position
    co.transform.position = a + (b - a) * 0.5f;

    // Scale it (horizontally or vertically)
    float dist = Vector2.Distance(a, b);
    if (a.x != b.x)
        co.transform.localScale = new Vector2(dist + 1, 1);
    else
        co.transform.localScale = new Vector2(1, dist + 1);
}

Se salvarmos o Script e pressionarmos Play , poderemos ver alguns cantos perfeitamente correspondentes:

Adicionando outro jogador

Tudo bem, vamos adicionar o segundo jogador ao nosso jogo. Começaremos clicando com o botão direito do mouse no GameObject player_cyan na Hierarquia e selecionando Duplicar :

Renomearemos o player duplicado para player_pink e mudaremos sua posição para (-3, 0, 0) . Enquanto estamos nisso, vamos também alterar as teclas de movimento para WSAD e arrastar o lightwall_pink Prefab para o slot Wall Prefab :

Se pressionarmos Play , agora podemos controlar dois jogadores, um com as teclas WSAD e outro com as teclas de seta :

Detecção de colisão

Tudo bem, então vamos adicionar uma condição de perda ao nosso jogo. Um jogador perderá o jogo sempre que bater em uma parede.

Já adicionamos os componentes de Física (Colliders e Rigidbodies) aos Lightwalls e aos players, então tudo o que precisamos fazer agora é adicionar uma nova função OnTriggerEnter2D ao nosso Move Script. Esta função será chamada automaticamente pelo Unity se um jogador colidir com algo:

void OnTriggerEnter2D(Collider2D co) {
    // Do Stuff...
}

O parâmetro ‘ Collider2D co ‘ é o Collider com o qual o jogador colidiu. Vamos ter certeza de que este Collider não é a parede que o jogador está arrastando atrás dele:

void OnTriggerEnter2D(Collider2D co) {
    // Not the current wall?
    if (co != wall) {
        // Do Stuff...
    }
}

Nesse caso deve ser qualquer outra parede, o que significa que o jogador perdeu o jogo. Vamos manter as coisas simples aqui e apenas destruir o jogador:

void OnTriggerEnter2D(Collider2D co) {
    // Not the current wall?
    if (co != wall) {
        print("Player lost: " + name);
        Destroy(gameObject);
    }
}

Nota: sinta-se à vontade para adicionar algum tipo de tela de vitória/perda neste momento.

Resumo

Acabamos de criar um jogo 2D estilo Tron Light-Cycles no Unity. Como sempre, a maioria dos recursos foi realmente fácil de implementar – graças ao poder do Unity Engine. O jogo oferece muito potencial, pois existem todos os tipos de recursos que ainda podem ser adicionados:

  • Tela de ganhar/perder
  • Mais de 2 jogadores
  • Multijogador on-line
  • IA
  • Alguns efeitos especiais
  • Sprites melhores

E ai, esta gostando da área de games? Então que tal se tornar um desenvolvedor de jogos completo com o curso Desenvolvedor de Jogos 2D e 3D. Neste curso você irá aprender na prática tudo sobre Game Design, Criação de artes para jogos, Lógica de Programação, Programação Orienta a Objetos e é claro, criar seus próprios jogos utilizando a Engine Unity.

FONTE: Esse texto foi retirado do site https://noobtuts.com/unity,

Super Dicas

Venha conhecer os nossos cursos da Hotmart Clube e Udemy.

Se inscreva em nosso canal, compartilhe as matérias que gostar com os seus colegas, e participe da nossa comunidade no Telegram.

Aproveite também e venha fazer parte do nosso clube de vantagens e ter acesso exclusivo a vídeos, tutoriais, cursos e muito mais. Clique no link para se tornar um membro do dfilitto – clube de vantagens e ter acesso a todos os benefícios do nosso clube.

Matheus Correia Augusto da Silva

Me chamo Matheus Correia, tenho 21 anos e curso graduação em Marketing, faculdade na qual termino em junho, em breve será lançado um livro no qual faço parte.
Faço estágio na empresa Micro2000 e faço parte do marketing de criação de imagens para redes sociais.

Assinar blog por e-mail

Digite seu endereço de e-mail para assinar este blog e receber notificações de novas publicações por e-mail.

Junte-se a 2.252 outros assinantes

Anúncios

Aprenda a criar seus jogos com os melhores desenvolvedores de jogos

Advertisement

Quer aprender a programar?