O que há de Novo?
Fórum Outer Space - O maior fórum de games do Brasil

Registre uma conta gratuita hoje para se tornar um membro! Uma vez conectado, você poderá participar neste site adicionando seus próprios tópicos e postagens, além de se conectar com outros membros por meio de sua própria caixa de entrada privada!

  • Anunciando os planos GOLD no Fórum Outer Space
    Visitante, agora você pode ajudar o Fórum Outer Space e receber alguns recursos exclusivos, incluindo navegação sem anúncios e dois temas exclusivos. Veja os detalhes aqui.


[Unity #02] Outer Space Invaders

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Outer_Space_Invaders.png

:: Tópicos

- Aperfeiçoando o Background com Parallax
- A Nossa Nave
- Os Monkey Invaders

# Aperfeiçoando o Background com Parallax

Na primeira parte da implementação do jogo, vimos como movimentar imagens em plano de fundo e fazê-las dar uma sensação de movimento continuo do espaço, agora vamos tornar essa sensação ainda melhor. A técnica de Parallax é largamente usada em jogos 2D da geração 16-BIT. Consiste basicamente em ter vários planos de fundo e movimentá-los paralelamente com velocidades diferente a fim de dar a sensação de profundidade.

Pense em um vasto campo verde com florestas e arbustos, depois delas vemos morros e mais ao fundo montanhas. Cada um desses conjuntos podem representar um background diferente e com velocidades diferentes. As florestas e arbustos por estarem mais perto de nossa vista se movem mais rápido, os morros estão mais afastados, portanto, se movem um pouco mais lentamente, já as montanhas que ficam mais longe ainda de tudo, devem se mover mais lentamente.

É claro que todos os backgrounds fazem parte da mesma profundidade, já que em jogos 2D a nossa visão é sempre ortogonal e não de perspectiva, porém usamos essa técnica para dar uma sensação de dimensão, de profundidade.

Wrath_of_the_Demon.gif

Scrolling Parallax do jogo Wrath of Demon

Para o nosso jogo, o parallax vai resultar em estrelas que estão mais perto e outras mais afastadas, mas você pode ficar livre em editar as imagens e colocar asteroides, planetas, etc.

Verifique se você copiou as outras 3 imagens restantes do background (SiderialSpace04, SiderialSpace05 e SiderialSpace06), faça com elas o mesmo esquema que você fez com as outras imagens. Certifique-se que estejam sem filtro (pela propriedade Filter Mode) e que estejam na posição inicial (0, 0, 0).

Agora estamos com um pequeno probleminha, como as imagens vão estar em planos diferentes, mesmo elas sendo de background, não podemos pôr as imagens 4, 5 e 6 no mesmo plano das outras três, porque senão não teríamos o efeito de parallax e nem sobreposição (overlap). Para que isso aconteça temos que separar as imagens por classificação de camada (Sorting Layer) e também por ordenação em camada (Order in Layer).

Em Sorting Layer, defimos as camadas propriamente ditas, são elas (Background e Foreground, ou qualquer outro nome que você queira dar), para as nossas 6 imagens de background, elas ficarão num mesmo Layer "Background", o que difere a ordenação entre elas será o Order In Layer. Selecione qualquer uma das imagens e teremos a seguinte imagem no Inspector:

02_001.jpg


Clique na opção "Default" da propriedade Sorting Layer e clique em "Add Sorting Layer". Adicione os layers de "Background" e "Foreground". Selecione agora todas as imagens e coloque "Background" como a opção de Sorting Layer. Em Order in Layer, os valores numéricos inferiores faz as imagens renderizar ao fundo, enquanto os valores maiores farão elas renderizar mais à frente. Selecione as imagens 1, 2 e 3 e coloque 5 em Order in Layer, as demais imagens ficarão com 0. Em resumo temos:

- SiderialSpace 1, 2 e 3
* Sorting Layer: "Background"
* Order in Layer: 5
- SiderialSpace 4, 5 e 6
* Sorting Layer: "Background"
* Order in Layer: 0


Adicione o componente de Box Collider 2D para cada um dos GameObjects das novas imagens e deixe a propriedade Is Trigger marcada. Não esqueça também da Tag "Space".

Na pasta de Scripts, crie um novo script chamado ScrollingParallax.cs. Abra e edite-o para ficar assim:

Código:
[System.Serializable]
public class ScrollingParallax
{
    public float speed = 1;
    public List<GameObject> images;
}

Vamos transferir a lógica de compor as imagens e a propriedade de velocidade para essa classe ao invés de deixá-las em BackgroundScroller. As palavras em colchetes são na verdade denominadas de atributos. Atributo é um recurso da linguagem C# para efetivar recursividade e pre-processamento de informações, para o nosso exemplo dizemos aqui para a Unity serializar a classe ScrollingParallax, dessa forma conseguiremos editar suas propriedades via Inspector, pois essa classe será acessada via Inspector através da classe BackgroundScroller, ou seja, não diretamente, se não serializarmos, não será possível de editar suas propriedades como queremos.

Retorne à classe BackgroundScroller e faremos a seguinte alteração:

- Apague a variável speed
- Altera a variável de backgrounds para:

Código:
public List<ScrollingParallax> parallaxes;

Em FixedUpdate deveremos fazer a seguinte alteração:

Código:
void FixedUpdate()
{
    foreach (var iParallax in parallaxes)
    {
        var speed = iParallax.speed;

        foreach (var iBackground in iParallax.images)
        {
            iBackground.transform.Translate(Time.deltaTime * speed * x * -1, Time.deltaTime * speed * y * -1, 0);
        }

    }
}

Como agora são na verdade duas listas, uma que percorre o parallax em si, a outra para percorrer cada uma das imagens de cada parallax, então temos que alterar todos os loops que antes eram apenas da lista de imagem.

No método Start, altere a parte relacionada ao loop que era de backgrounds para:

Código:
foreach (var iParallax in parallaxes)
{
    amountX = 0;
    amountY = 0;

    foreach (var iBackground in iParallax.images)
    {
        var sr = iBackground.GetComponent<SpriteRenderer>();

        if (x > 0 || x < 0)
            iBackground.transform.position = new Vector3((amountX + offset) * x, 0);
        else if (y > 0 || y < 0)
            iBackground.transform.position = new Vector3(0, (amountY + offset) * y);

        amountX += sr.bounds.size.x;
        amountY += sr.bounds.size.y;
    }
}

Cada vez que percorremos um parallax, resetamos o amountX e amountY, dessa vez reposicionamos todas as imagens de um novo parallax a partir da posição inicial (0, 0), se não fizermos isso, o algoritmo irá concatenar todas as 6 imagens e não teremos o efeito desejado.

Logo abaixo desse último loop, mude a chamada à lista de backgrounds para:

Código:
var background = parallaxes[0].images[0];

Novamente, considerando todas as imagens com a mesma largura e mesma altura, então basta pegar qualquer uma delas que está válido. Caso você queira criar um sistema que abrange mais situações, possa ser interessante determinar a distância do SwapBar à nossa câmera de uma outra maneira.

Altere agora o método GetBackgroundIndex para:

Código:
private int GetBackgroundIndex(GameObject background, out ScrollingParallax parallax)
{
    parallax = null;

    foreach (var iBackground in parallaxes)
    {
        for (var i = 0; i < iBackground.images.Count; i++)
        {
            if (background == iBackground.images[i])
            {
                parallax = iBackground;
                return i;
            }
        }
    }

    return -1;
}

Veja que a assinatura desse método mudou. Usando a keyword "out", fará com que retornemos um valor à variável ScrollingParallax quando o programa sair desse método, ou seja, além do índice para saber a posição que a imagem que tocou o SwapBar se encontra, temos que saber em qual parallax ele também está. Vemos o resultado disso no próximo método, o RepositionSpace. A keyword "out" é uma forma elegante de fazer na prática um método retornar múltiplos valores. :ksafado

Código:
public void RepositionSpace(GameObject go)
{
    //em qual parallax a imagem se encontra
    ScrollingParallax parallax;
    //a partir do índice atual do GO
    var index = GetBackgroundIndex(go, out parallax);

    //pega o índice da última imagem
    for (var i = 0; i < parallax.images.Count - 1; i++)
    {
        index++;

        if (index > parallax.images.Count - 1)
            index = 0;
    }

    //calcula a nova posição da imagem a partir do seu índice
    var lastBackgroundPosition = parallax.images[index].transform.position;
    go.transform.position = new Vector3(lastBackgroundPosition.x + sizeX * x, lastBackgroundPosition.y + sizeY * y);
}

Nossa classe BackgroundScroller deve ficar assim:

BackgroundScroller_06

codigo.png


Em nossa Hierarchy, clique em no GameObject BackgroundScroller e repare que as imagens que antes estavam associadas não estão mais. Isso se deve porque substituímos a lista backgrounds pela lista parallaxes, então a Unity removeu do Inspector a referência. Agora é só atualizarmos os dados novamente. Vamos ter dois parallaxes e três imagens em cada um.

giphy.gif


Se tudo estiver OK, teremos esse efeito quando executarmos o jogo:

giphy.gif


Eu sei que o progresso pode ter parecido pouco, mas nessa altura você sabe usar duas técnicas bem comuns em jogos, que é a infinity scrolling e parallax. :kjoinha

# A Nossa Nave

BD7BbEd.png


Chegou a tão esperada hora de fazer a nossa nave. Ela, toda poderosa que irá detonar a cara dos Emotikongs ( :ksnif coitadinhos).

Importe o arquivo de imagem de alguma das Spaceship à pasta de Textures em nosso projeto (importei a da alpha). Coloque o Filter Mode para Point (no Filter). Arraste a imagem até a área da nossa câmera. Se executarmos o jogo agora, veremos que a imagem da nave está um pouco pequena, isso porque a medida unit está configurada para 100 pixels, mudemos para 32 (caso você pegou a nave de 32x32).

eIVhIWK.jpg


Agora a nave parece mais ajustada ao nosso jogo. No GameObject que foi criado em nossa Hierarquia, vamos configurar o Sorting Layer para Foreground e deixar o Order in Layer em 10 (pode ser que vamos precisar de algum objeto foreground que fique renderizado atrás à nave). Se você criou os layers em ordem correta, todo objeto em Foreground irá prevalecer em cima de Background, então a nossa nave sempre será renderizada pela Unity à frente das estrelas.

Ainda no Inspector de nossa nave, selecione a Tag "Player" (algumas tags já estão disponíveis em todo projeto), adicione o componente Box Collider 2D e deixe Is Trigger marcado. Redimensione a área de colisão para ficar mais ajustada possível ao tamanho da nave, caso necessário. No caso da SpaceshipAlpha, eu deixei a seguinte configuração:

arE99Cr.jpg


Adicione o componente RigidBody 2D e na propriedade Body Type escolha a opção "Kinematic", assim poderemos manipular a nave sem que ela sofra ação da gravidade.

NOTA - O poder do Kinematic

Objetos quinemáticos, são úteis quando queremos ter controle de seus movimentos e ações. São usados no lugar dos objetos dinâmicos que são objetos que sofrem ação do sistema de física implementado pela engine que você estiver usando. Sendo assim, geralmente objetos que são personagens (controlados ou não pelo jogador) podem ser configurados como quinemáticos, geralmente as solução apliacadas para um personagem em plataforma 2D é também quinemático. Ainda assim, objetos desse tipo, acabam fazendo uso de uma parte do sistema de física, como é o caso das colisões calculadas dinamicamente, seja Box2D, Bullet ou qualquer outro sistema de física.​

Posicione a nave mais ou menos na parte de baixo centralizada (com x em zero):

790IY02.jpg


Agora crie um novo script chamado Player.cs (lembre-se que toda a criação de script via Unity não precisa digitar o ".cs" no final) abra-o e mude de Update para FixedUpdate e adicione essas duas variáveis como sendo variáveis de classe:

Código:
public float speed = 10;

private SpriteRenderer spriteRenderer;

A variável speed como o nome já diz será a velocidade de deslocamento da nave, enquanto spriteRender será necessário para obter o sprite da nave para se obter a sua dimensão, além de outras coisas.

Geralmente o método Awake é utilizado se obter a referência dos componentes associados ao Game Object, enquanto o método Start é mais para inicialização de variáveis e estados. Adicione o método Awake com o seguinte conteúdo:

Código:
spriteRenderer = GetComponent<SpriteRenderer>();

No FixedUpdate, coloque temporariamente:

Código:
var horizontal = Input.GetAxis("Horizontal");

Criamos uma variável chamada horizontal que funcionará como valores para o eixo horizontal. GetAxis é usado para se obter valores de eixos, como Horizontal ou Vertical, de teclado ou mesmo joystick. A classe Input é que fornece toda a estrutura para tratarmos dos inputs do usuário via teclado, mouse, joystick e até mesmo touch.

Em "Edit > Project Settings > Input" veremos suas propriedades:

TocKrRh.jpg


É nela que todos os nossos inputs estão configurados. Alguns já estão configurados já de antemão, mas o usuário é livre para colocar ou retirar quais inputs ele desejar. Os nomes que vemos são os mesmos usados para se obter tais eixos. Alguns eixos se repetem porque são configurados para mais uma de uma input, exemplo de Horizontal e Vertical que são configurados tanto para teclado como para joystick.

Abra o primeiro eixo horizontal e teremos essa tela:

PMUC0pv.jpg


A propriedade Name será usada para obtermos o nome do eixo via código, usando métodos como GetAxis ou mesmo GetButton (mas existem outros). Negative Button e Positive Button servem para informar qual parte do eixo dará um valor negativo e qual parte dará um positivo respectivamente. Uma vez usando o eixo dessa forma, só poderemos usar o GetAxis para obter seus valores para uma variável do tipo float. Esses valores variam entre -1 e 1, sendo 0 o estado neutro. Com os eixos também teremos a ideia de aceleração, quando você aperta muito forte, por exemplo, o analógico de um joystick, ele vai mais rápido, obtendo -1 ou 1 a depender do seu sentido, se apertarmos devagar, os valores serão algo bem menor, a exemplo -0.1, -0.4, 0.3, 0.7. Se não quisermos aceleração nenhuma é também muito simples de se resolver, basta verificar se a variável é maior que 0, então seta o valor como sempre sendo 1 ou menor que 0, setando a variável sempre sempre como -1. :ksafado

Abra agora um outro eixo, o Jump.

Va04BQj.jpg


Repare que não temos o valor negativo informado, isso porque na verdade o Jump não é um eixo e sim um botão em nessa ocasião. Poderemos usar o GetButton para obter o seu valor, nesse caso, ele testa se a tecla informada (propriedade Positive Button) foi pressionada. Ou seja, quando o usuário apertar a tecla "espaço", ele na verdade tá chamando o eixo Jump. Poderemos testar dessa forma:

Código:
var jump = Input.GetButton("Jump");

if (jump)
    Debug.Log("Jogador apertou a tecla Space!");

Espero que tenha ficado claro o funcionamento inicial desses recursos. Apague a parte relacionada ao Jump (voltaremos mais à frente nela). Coloque agora o seguinte código logo após a inicialização da variável horizontal:

Código:
transform.Translate(horizontal * speed * Time.deltaTime, 0, 0);

Lembra do Translate que usamos para mover os planos de fundo das estrelas? Usamos ele aqui novamente para mover a nave pelo eixo X. Temos já o valor de horizontal pego anteriormente, esse valor como já sabemos pode variar entre menor que zero (esquerda), 0 (parado) e maior que zero (direita), multiplicamos pela velocidade e sempre pelo deltaTime. Os demais eixos (y e z) deixamos em zero porque não queremos mover a nave por eles.

Selecione pela Hierarchy a sua nave e adicione o componente Player.cs, caso ainda não tenha feito. Execute o jogo e verifique se a nave se move entre à esquerda e à direita.

Agora temos um pequeno probleminha...

n3Nddam.png


É fácil notar que ao mover a nave ela acaba saindo completamente de fora do nosso cenário, consequentemente a câmera não consegue captá-la mais. Temos que limitar a quantidade de espaço que a nave consegue percorrer para a área da nossa câmera.

Para isso, teremos que obter a extensão da câmera tanto na vertical quanto horizontal (já que para calcular a extensão horizontal precisamos da vertical) e também a dimensão da nave (para isso que inicializamos no início do código o SpriteRenderer).

Selecione o GameObject da nossa câmera na hierarquia. Temos as propriedades Projection e Size. Nossa câmera é tipo Ortographic, dessa forma, as câmeras ortogonais não darão a ideia de profundidade, sendo ideais para jogos 2D. A propriedade "size" é o tamanho da câmera em sua vertical, vamos pegá-lo para calcular a extensão horizontal e limitar a movimentação da nave.

Jdb2Nj9.jpg


Em Player.cs logo abaixo do código do Translate. Coloque:

Código:
var camExtentV = Camera.main.orthographicSize;
var camExtentH = Camera.main.aspect * camExtentV;

A extensão horizontal é o cálculo da multiplicação da extensão vertical pelo aspect ratio. Temos que fazer que a cada execução do FixedUpdate além do Translate mover a nave, reposicionamos a nave para fora das dimensões da tela em sua área horizontal. Vamos portanto alterar o transform.position da nave para o valor corrente do X dela (caso esse x não ultrapassou os limites da tela) ou caso ele tente atravessar, limitaremos ele para os valores da extrema esquerda ou extrema direita. Para fazer essa limitação, usamos uma função matemática chamada Clamp.

Voltando à câmera, o pivô dela está exatamente em seu centro. Dessa forma, calculemos a extensão a partir da sua posição original que está no zero. A posição da câmera em x menos a extensão horizontal, e a posição da câmera em x mais a extensão horizontal.

30pexvL.jpg


Em 1 temos o pivot do câmera. Em 2 e 3 temos a nossa extensão calculada agora pelo:

Código:
var camMinX = Camera.main.transform.position.x - camExtentH; //2
var camMaxX = Camera.main.transform.position.x + camExtentH; //3

Agora alteramos a posição da nave para o valor atual de X da posição dela ou para os valores camMinX ou camMaxX, caso necessário.

Código:
transform.position = new Vector3(Mathf.Clamp(transform.position.x, camMinX, camMaxX), transform.position.y, 0);

Em Clamp temos como parâmetros: Mathf.Clamp(valor atual, valor mínimo, valor máximo).

Execute o código agora e tente sair do cenário com a nave. Bom, teremos uma meia grata surpresa ¯\_(ツ)_/¯ , metade da nave fica pra fora e a outra metade para dentro.
0AKA0yc.png

Calma, Kiança, na verdade o código tá funcionando. O problema é que temos que calcular à dimensão em relação à nave também, já que o pivot dela está no centro. Então quando usamos no "clamp" o "transform.position.x", não estamos pegando as extremidades dela, mas a posição central, a posição do seu pivot.

JdYPXuk.jpg


O pivot de uma imagem não é determinada pelo seu GameObject, diferente da posição de renderização de uma imagem que é definida pelo GameObject como já vimos. É na própria textura que definimos qual será o pivot de uma imagem, e a partir desse pivot que vamos definir a sua posição no espaço. Para vermos o pivot em nossa Scene, clique no GameObject e aperte a tecla W. Em nossa textura definimos o pivot de uma imagem:

RlVBVaJ.jpg


NOTA - Compreender como funcionam os Pivots em Unity é fundamental para trabalhar com posição e deslocamento de objetos

Pivot é uma parte fundamente da Unity, é nele que diz onde no espaço o nosso GameObject será renderizado. Não existe uma regra para se trabalhar com eles, é necessário saber que objetos com pivots diferentes poderão resultar em posições indesejadas como colocados um ao lado do outro. É o pivot que informa que para uma mesma posição x, y e z, objetos poderão se posicionar diferentemente, mesmo com os mesmo valores, pois é o pivot que determina que parte da objeto ficará naquelas valore.​

Voltando ao nosso código... agora que sabemos mais ou menos como as coisas funcionam, vamos ajustar as variáveis de camMinX e camMaxX para de fato corresponder aos nossos valores desejados de posicionamento. Altere elas para:

Código:
var camMinX = Camera.main.transform.position.x - (camExtentH + spriteRenderer.sprite.bounds.min.x);
var camMaxX = Camera.main.transform.position.x + (camExtentH - spriteRenderer.sprite.bounds.max.x);

Nossa Player.cs até dado momento:

Player_01.cs

Execute o jogo e vê que a nave agora se encaixa direito na posição horizontal sem sair da tela. Acessando o sprite pelo SpriteRender pegamos seus limites (bounds) mínimo e máximo, e calculamos junto a extensão horizontal com base na posição da câmera em X.

# Os Monkey Invaders

Acabamos a nossa primeira parte da nave, vamos pôr um pouco ação para deixar a festa começar. Os nossos queridos EmotiKongs nesse jogo serão os Monkey Invaders. Macacos siderais cruéis à procura de recompensa. As bananas estão caras, é necessário caçar e extorquir naves desavisadas circulando por aí no espaço dando bobeira. ¯\_(ツ)_/¯

Importe as texturas dos EmotiKongs que temos e faça as seguintes configurações nas imagens:

- kClassic, kFeliz, kLaco, kVergonha
* Pixel Per Unit: 32
* Pivot: Center
* Filter Mode: Point (no filter)
- kForeverAlone
* Pixel Per Unit: 16
* Pivot: Center
* Filter Mode: Point (no filter)


Vamos criar um novo script chamado de Enemy.cs. Arraste a textura de kClassic à cena para a Unity criar o seu Game Object. Arraste o componente Enemy.cs até o nosso kClassic ou clique em "Add Component" e digite o nome do script. Renomeio o GO de "kClassic" para "KongClassic", assim ele fica mais apresentável em nossa hierarquia.

Crie uma nova Tag chamada "Monster" e a selecione para o nosso Kong. Adicione agora o componente Circle Collider 2D no KongClassic e deixe Is Trigger marcado. Esse collider faz a mesma coisa que o Box Collider 2D, no entanto, a área de colisão que ele gera é circular. Poderíamos continuar usando o Box Collider para quase todas as situações, inclusive essa, mas vamos mudar um pouco a nossa rotina. Como aqui é um tutorial cujo o intuito é aprender mostrando as diversas formas de se fazer uma coisa, então eu mudei apenas por medida educacional.

NOTA - Colliders circulares podem comprometer a performance.

Collider que apresentam sua forma mais complexa como colliders personalizados ou circulares podem comprometer a performance de um jogo se forem usados ao extremo. Um collider em forma retangular efetua menos cálculos de física para saber se um objeto passou ou tocou nele, já um collider circular para a mesma situação faz muito mais cálculos. Então, saber dosar qual collider usar é importante para se ter uma performance adequada, principalmente se você for lançar seus jogos para celular ou máquinas mais antigas.​

Ajuste no componente do collider a propriedade radius para melhor se encaixar na cabeça do kong. Deixei em "0.38". Ainda no Inspector do KongClassic, altere as propriedades Sorting Layer para "Foreground" e Order in Layer para "10".

Dê ao nosso KongClassic o componente RigidBody 2D com a propriedade Body Type configurada para "Kinematic".

Selecione o KongClassic na Hierarchy e com o botão do mouse do mouse direito selecione "Create Empty". Vamos adicionar um outro Game Object só que dessa vez aninhado ao nosso Kong. Podemos criar o GameObject fora e arrastá-lo até o KongClassic, se preferir. O importante é deixar ele sendo um objeto filho de KongClassic. Renomeie-o para BulletPoint. Deveremos ter algo como é mostrado na imagem abaixo:

1B9cRhH.jpg


Agora com o BulletPoint selecionado, poderemos definir uma marcação para melhor visualizar os Game Objects que são "invisíveis" à cena. Essa marcação é apenas para debugar o nosso código, como se fosse um "Place Holder". Elas não são renderizadas em tempo de execução do jogo. Selecione algum dos ícones disponíveis e veremos ele renderizado no lugar do Game Object, mostrando ainda o seu nome.

egLf3e6.jpg


Agora reposicione o BulletPoint até a boca do Kong, é justamente a posição dele que iremos usar para fazer o Kong atirar.

IRNaRoA.jpg


Crie o script Enemy.cs e adicione as seguintes variáveis de classe:

Código:
public float speed = 3;
public int hp = 10;
public int damage = 10;

Algumas propriedades e códigos que vamos fazer nessa classe serão alteradas mais à frente, principalmente na parte das waves, por enquanto, por medida de testes, vamos deixar como tá, fazendo tudo em partes, como já dizia Jack. :rox

Speed é a velocidade de movimento do inimigo, HP é justamente o seus hit points e a variável damage é o dano que o inimigo faz no player quando toca o mesmo.

Em FixedUpdate coloque:

Código:
transform.Translate(0, - speed * Time.deltaTime, 0);

Até esse momento, isso aqui não é novidade para ninguém, o sinal negativo no parâmetro "y" do Translate é pra informar que o objeto irá se mover pra baixo ao invés de ir pra cima.

Edite novamente o arquivo Player.cs e coloque a seguinte variável após a variável "speed":

Código:
public int hp = 30;

Embaixo do método FixedUpdate, coloque os seguintes métodos:

Código:
public void Kill()
{
    DestroyImmediate(gameObject);
}

public void Hit(int damage)
{
    hp -= damage;

    if (hp <= 0)
        Kill();
}

O método Hit é chamado pelo monstro quando ele colide com o player, caso o HP fique menor ou igual a zero, então o método Kill é chamado matando o player imediatamente (valor elaborar a morte bonitinha com Game Over e tudo mais numa outra parte). Vale lembrar em colocar as Tags de "Monster" e "Player" para que a colisão consiga filtrar para os objetos corretos.

Edite a classe Enemy.cs novamente e adicione o seguinte método em seu final:

Código:
private void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag("Player"))
    {
        other.GetComponent<Player>().Hit(damage);
    }
}

Em nossa cena, posicione o monstro em um mesmo alinhamento da nave, só que acima dele. Se tudo estiver certo, o HP do jogador diminui conforme o inimigo o atinja.

source.gif


Importe agora as imagens das Bullets em Textures. Configure-as conforme descrito:

- Bullet01
* Pixel Per Unit: 64
* Pivot: Bottom
* Filter Mode: Point (no filter)
- Bullet02, Bullet03
* Pixel Per Unit: 128
* Pivot: Center
* Filter Mode: Point (no filter)


Bullet01 usaremos como a munição do Player, enquanto as demais serão as munições dos inimigos.

Arraste a Bullet01 à cena, renomeie o seu Game Object para BulletPlayer. Coloque o Sorting Layer para "Foreground" e o Order in Layer para "15". Adicione também o componente Box Collider 2D nela, deixe a propriedade Is Trigger marcada.

Crie agora um novo script chamado Bullet.cs e o adicione como componente ao BulletPlayer.

Dentro dele coloque essas variáveis de classe:

Código:
public float speed = 10;
public int damage = 10;
public Origin caster;
[HideInInspector] public GameObject target;

private string targetTag;

Toda bala terá uma velocidade de deslocamento assim como o dano que ela é capaz de fazer. A variável caster é pra marcar quem "castou" a bala, que no caso pode ser tanto o jogador quanto os inimigos, isso definido justamente na enum Origin que vem logo a seguir:

Código:
public enum Origin
{
    Player,
    Enemy
}

O atributo HideInInspector como o nome já diz esconde a variável da vista do Inspector. Não há necessidade do nosso target ser visível no Inspector mas, ao mesmo tempo, queremos que ele seja público porque vamos acessá-lo através da classe BulletAbstract.

No método Start de Bullet.cs faça:

Código:
if (caster == Origin.Player)
    targetTag = "Enemy";
else
    targetTag = "Player";

Aqui ele verifica se quem castou a bala foi o jogador, então a colisão deve ser feita contra os monstros, caso quem castou foi um monstro, então a colisão deve ser com o jogador.

Falando em colisão, deveremos verificar se ela ocorre no próximo método:

Código:
private void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag(targetTag))
    {
        target = other.gameObject;
    }
}

Quando a colisão ocorrer pela identificação do targetTag, então preenchemos a variável target com o objeto que colidiu. Dessa forma as nossas próximas classes saberão quem colidiu com a bala e tomará as medidas de acordo com isso.

Agora crie a classe BulletAbstract.cs e faça conforme o código abaixo:

Código:
public abstract class BulletAbstract : MonoBehaviour
{
    public abstract void Movement();
    public abstract void Hit();
    public abstract void Kill();

    protected Bullet bullet;

    private void Awake()
    {
        bullet = GetComponent<Bullet>();
    }

    private void FixedUpdate()
    {
        //move a bala
        Movement();

        if (bullet.target != null)
        {
            //acerta o alvo
            Hit();
            //destroi a bala
            Kill();
        }
    }
}

BulletAbstract é uma classe abstrata. Não é o intuito desse curso explicar os pormenores da programação em C#, nem focar em conceitos de OO (orientação a objeto), mas caso você seja completamente iniciante em programação, farei um breve resumo aqui.

Uma classe abstrata é como se fosse um molde que serve para outras classes oriundas dela. Esse molde define algumas regras que todas classes que herdam dela devem seguir, a questão é, que cada classe que a herde deve ter as mesmas regras, porém a implementação delas podem ser diferentes. Indo de encontro à nossa realidade aqui, posso ter BulletPlayer, BulletEnemyStraight, BulletEnemySilhouette etc, todas terão os métodos acima, ou seja, todas elas terão um método para movimento, outro para quando acerta o alvo e outra qual a sua própria morte, porém, cada uma pode implementar isso de forma diferente, é por isso que "componetizei" ainda mais a classe bullet em outras classes derivadas a partir de BulletAbstract.

Quando definimos métodos abstratos em uma classe abstrata, estamos dizendo que eles serão implementados na classe concreta que deriva da nossa classe abstrata. Para exemplificar novamente tudo isso, eu posso ter uma classe abstrata chamada Animal e três outras classes que derivam/herdam dela, a classe Cachorro, a classe Gato e a classe Papagaio, todas as três são classes concretas. Em Animal eu defini os métodos Comer e Barulho. O que há de comum entre um cachorro, um gato e um papagaio é que todos eles são animais, portanto todos eles comem e fazem barulhos, porém cada um pode ter uma forma diferente de comer e fazer barulho. O cachorro come mordendo a carne, o gato também, mas o papagaio não. O cachorro faz barulho latindo, o gato miado e o papagaio palrando.

giphy.gif


Métodos abstratos só podem ser implementados na primeira classe concreta que herde da classe abstrata. A ideia aqui é ter várias implementações de comportamento de balas diferentes uma para cada situação que eu precisar definir. O que há em comum entre elas, fica tudo restringido à classe Bullet.cs, o que há em comum entre elas mas com execução diferente, fica a critério de classes especializadas e próprias herdadas de BulletAbstract.cs.

Em Awake pegamos o componente Bullet, vamos precisar dele principalmente para saber se temos um alvo ou não (variável target preenchida pelo método OnTriggerEnter2D em Bullet).

Em FixedUpdate executamos constantemente o movimento da bala, repare que a sua implementação não foi feita aqui, não cabe à BulletAbstract saber como esse método funciona, quem implementá-la é que deve dizer como os métodos abstratos funcionarão. O que importa em BulletAbstract é: movimente a bala até achar um alvo, achando um alvo, então o acerte e se mate (no caso da bala).

Crie agora a classe BulletPlayer.cs, a sua definição completa está logo a seguir:

Código:
public class BulletPlayer : BulletAbstract
{
    public override void Movement()
    {
        transform.Translate(0, bullet.speed * Time.deltaTime, 0);
    }

    public override void Hit()
    {
        var enemy = bullet.target.GetComponent<Enemy>();

        enemy.hp -= bullet.damage;

        if (enemy.hp <= 0)
        {
            enemy.Kill();
        }
    }

    public override void Kill()
    {
        DestroyImmediate(gameObject);
    }
}

A primeira coisa que poderemos notar é que BulletPlayer não herda de MonoBehaviour como de costume, ela herda de BulletAbstract isso porque ela será uma especialização dessa e, portanto, uma de suas classes concretas. Os métodos Movement, Hit e Kill que foram definidos como abstratos em BulletAbstract são agora implementados em BulletPlayer. Qualquer classe que herde de BulletAbstract OBRIGATORIAMENTE terá que implementá-los, desde que essa classe seja concreta (sem a definição de abstract).

A palavra chave override deve ser usada para indicar que iremos implementar um método abstrato. Agora BulletPlayer tem o seu próprio comportamento quando atiramos com o jogador e acertamos um inimigo.

0e69b0_3742762.jpg


Adicione o componente BulletPlayer.cs ao Game Object de BulletPlayer em nossa Hierarchy. Agora algo totalmente novo: Arraste esse objeto até a pasta Prefab que definimos em Project. Agora esse GO terá uma cor azulada na Hierarchy, essa cor tem um propósito que é indicar que esse objeto é um Prefab. Falamos superficialmente sobre os Prefabs no início desse curso, quando criamos essa pasta.

QO4Gzt5.jpg


Prefabs serão todos aquele objeto que precisamos reutilizar no jogo. É um objeto semi-pronto que posso precisar de várias instâncias dele na tela. A nossa nave está o tempo todo atirando, para cada tiro que dá, eu tô chamando o prefab dessa bala. Os inimigos também serão prefabs, assim como também o Player (mesmo esse objeto não se repetindo várias vezes numa única cena, ele pode ser chamados diversas vezes no jogo em cada fase/cena diferente), então é conveniente criar todos esses game object listados como prefabs.

Podemos criá-los também diretamente pela janela Project indo com o botão direito do mouse e selecionando "Create > Prefab", mas, sinceramente, não vejo ninguém usar dessa forma. Falando em Project, é assim como um prefab fica quando criado:

pK2gpuR.jpg


Agora deveremos fazer com que o player crie instâncias de BulletPlayer toda vez que atirar. Vamos trabalhar com o Player novamente

RNnkeFk.png


Crie o GameObject "FirePoint" dentro do GameObject de nossa nave (é exatamente da mesma forma que fizemos com o KongClassic). Arraste-o até uma posição que indique de onde tiro irá sair.

boQ56op.jpg


Edite a classe Player.cs e adicione essas novas variáveis de classe:

Código:
public GameObject bulletPrefab;
public GameObject firePoint;
public float fireRate = 0.3f;

Variável bulletPrefab receberá o nosso prefab, e firePoint o local que as balas são instanciadas, enquanto fireRate será a nossa frequência de disparo, ou a nossa cadência de tiro. Salve o arquivo e no Inspector arraste tanto o prefab da bala quanto o FirePoint que está dentro do Player para essas variáveis:

02n5y9W.jpg


Agora de volta ao arquivo, crie as próximas variáveis privadas (lembre-se de deixar separadas as variáveis e métodos por escopo de visibilidade):

Código:
private float horizontal;
private float fireCounter;

Remova de FixedUpdate a linha relacionada ao input do horizontal (cuidado para não remover o Translate). Crie o método Update acima do FixedUpdate. E adicione dentro dele:

Código:
horizontal = Input.GetAxis("Horizontal");
var jump = Input.GetButton("Jump");

if (jump)
{

}

Quando o usuário apertar a tecla espaço (nomeada como "Jump" pela Unity, como vimos lá atrás), deveremos atirar. Para isso, a variável fireCounter irá contar até chegar no fireRate, quanto maior for o fireRate, mais lento será a cadência do tiro.

Ao fireCounter chegar na mesma quantidade de tempo do fireRate, então a variável é resetada e um tiro é disparado. O Update ficou:

Código:
private void Update()
{
    horizontal = Input.GetAxis("Horizontal");
    var jump = Input.GetButton("Jump");

    if (jump)
    {
        fireCounter += Time.deltaTime;

        if (fireCounter >= fireRate)
        {
            fireCounter = 0;
            Instantiate(bulletPrefab, firePoint.transform.position, Quaternion.identity);
        }
    }
}

O método Instantiate é o responsável por instanciar o nosso prefab. Podemos lê-lo assim: Instantiate(prefab utilizado, posição de nascimento, orientação da sua rotação); Esse método retorna um GameObject (que é o GO do prefab instanciado), porém, como não precisamos manipulá-lo pela classe Player.cs, então não precisou fazer esse retorno.

giphy.gif


Se tudo estiver OK, poderemos já ver uma pequena cena de como o jogo final mais ou menos estará, ainda faltam coisas que deixaremos para o próximo capítulo, remoção das balas que não acertaram nada, criação de waves de monstros, tiro dos inimigos, HUD para indicar life do jogador ou qualquer outra informação, sons, tela inicial tela de game over, etc. :rox

Nossos arquivos finais para esse capítulo ficaram:

Player_02
Enemy_01
Bullet_01
BulletAbstract_01
BulletPlayer_01

PARTE III
 
Ultima Edição:

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Depois de alguns dias parados sem mexer, hoje tive um tempinho e consegui voltar ao tutorial mas tive outro probleminha.
Quando coloquei em 2 o numero de parallaxes no inspector do BackgroundScroller, ele não me deixa colocar a speed e as imagens em cada parallax, fica assim como na imagem:

3vMS3e7.jpg


E também a Swapbar aparece como none(Game Object)
 

sparcx86_GHOST

Ei mãe, 500 pontos!
Mensagens
26.774
Reações
18.254
Pontos
784
Pelo pouco que li desta aula vi que tens muita capacidade de explicar as coisas, devia copiar este curso para o site Coursera e publicar, daria um curso e tanto.
Parabéns.
3FD4810AC429D648466EB67E3D6E0E3C
 

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Depois de alguns dias parados sem mexer, hoje tive um tempinho e consegui voltar ao tutorial mas tive outro probleminha.
Quando coloquei em 2 o numero de parallaxes no inspector do BackgroundScroller, ele não me deixa colocar a speed e as imagens em cada parallax, fica assim como na imagem:

3vMS3e7.jpg


E também a Swapbar aparece como none(Game Object)

Nessa altura, o seu BackgroundScroller tem que ficar assim:

7aopaY3.jpg

Você tem 2 parallaxes, sendo que cada um dele têm 3 imagens. Dando um total de 6 imagens. 3 imagens que ficarão mais sobrepostas (overlap) e 3 que ficarão mais ao fundo e andarão mais lentamente.

Veja se você implementou a classe ScrollingParallax, já que ela não tá aparecendo na sua lista de parallax:

Código:
[System.Serializable]
public class ScrollingParallax
{
    public float speed = 1;
    public List<GameObject> images;
}

Vale lembrar que essa classe difere das outras que estávamos trabalhando porque ela tem que aparecer no Inspector de uma outra classe (que no caso é a BackgroundParallax), dessa forma, para criar essa reflexão, é necessário fazê-la ser serializada e não herdar de MonoBehaviour.

Infelizmente esses termos podem soar um pouco complexo, é assim que a Unity trabalha quando tentamos detalhar um pouco mais.

Eu tenho medo que, depois de analisar o que eu já fiz e nem publiquei ainda, que o curso pode parecer um pouco hermético para os que tão começando agora, talvez porque embarguei em usar algumas técnicas diferentes, mesmo o jogo sendo visualmente extremamente simples.
 

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Pelo pouco que li desta aula vi que tens muita capacidade de explicar as coisas, devia copiar este curso para o site Coursera e publicar, daria um curso e tanto.
Parabéns.
3FD4810AC429D648466EB67E3D6E0E3C

Na verdade, estou com um outro projeto em fundar um site de cursos para Unity voltado ao público de língua portuguesa. Mas ainda tá bem inicial, tendo algo concluído em irei divulgar aqui também.

----------------------------------------

QUARTA PARTE DO CURSO SAI HOJE.

Tirando o guerreiro @thiagoencd que parece ser a única pessoa nessa incursão do medo de tentar seguir esse tutorial louco. :kvergonha

A quarta parte sai hoje, ela será um pouco mais curta e até mais simples que a última que acho foi a mais tensa, até agora. Depois dessa quarta parte, pretendo ainda lançar mais uma, como dito anteriormente.

Se alguém mais estiver tentando seguir os tutoriais, por favor, se manifeste, assim eu fico sabendo o feedback de vocês e ver o que eu preciso fazer para melhorar os cursos, além disso me servir como incentivo para talvez fazer novos cursos.
 

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Veja se você implementou a classe ScrollingParallax, já que ela não tá aparecendo na sua lista de parallax:

Código:
[System.Serializable]
public class ScrollingParallax
{
    public float speed = 1;
    public List<GameObject> images;
}

Vale lembrar que essa classe difere das outras que estávamos trabalhando porque ela tem que aparecer no Inspector de uma outra classe (que no caso é a BackgroundParallax), dessa forma, para criar essa reflexão, é necessário fazê-la ser serializada e não herdar de MonoBehaviour.

Talvez seja isso então, na explicação ela tá herdando de MonoBehaviour, aí acabei fazendo desse jeito e como mostrava erro, acabei incluindo Using UnityEngine, eu acho. Depois se puder conserta lá na explicação.

Vou tentar dar uma olhada quando chegar em casa, valeu!!!
 


Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Talvez seja isso então, na explicação ela tá herdando de MonoBehaviour, aí acabei fazendo desse jeito e como mostrava erro, acabei incluindo Using UnityEngine, eu acho. Depois se puder conserta lá na explicação.

Vou tentar dar uma olhada quando chegar em casa, valeu!!!

Tá vendo que é importante o feedback?! :)

Agora que eu vi que deixei o código diferente do que implementei. Acabei não tendo tempo de revisar e deixei registrado aqui de forma errada. Peço desculpas a todos por isso.

Eu acabo adiantando os códigos antes mesmo de mostrar, depois é que eu paro para digitar o tutorial, então como não sou eu que tô fazendo o curso (no sentido de acompanhar as aulas) acabo não tendo a noção exata de como vocês estão o recebendo.

Vou alterar na fonte do tutorial.
 

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Valeu @Landstalker !!!
Esse curso veio na hora certa pra mim pois to vendo isso na pós, tenho outro monte de cursos comprados para fazer, mas sem tempo, então arrumei um tempo pra esse pois acaba me ajudando bem na pós também.
 

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Só complementando a explicação do ScrollingParallax...

Antes, só existia um único plano de scrolling, um scrolling infinito das imagens do espaço. Nesse capítulo, contudo, resolvi criar além de um scroll infinito o efeito de parallax que é bastante comum nos jogos 2D, principalmente os da geração 16-BIT (eu não sei ou ao menos me recordo se teve algum jogo 8-BIT com esse efeito, mas enfim...)

A minha ideia é criar os códigos da BackgroundScroller de forma universal que qualquer um possa depois de implementados usar em qualquer jogo que a pessoa venha a desenvolver de forma simples.

Então agora a BackgroundScroller suporta não só scrolling infinito como Parallax. Mas os dados das imagens não mais estarão diretos de uma lista, agora a lista aponta para uma outra classe, a ScrollingParallax. E como usamos uma lista de classe (decorre esse termo), então é necessário serializar para que o Inspector consiga enxergá-la. Antes não precisávamos disso pois a lista era de imagens, e as informações como speed ficavam na própria BackgroundScroller.
 

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
Já adianto que a quarta parte será mais simples que essa e um pouco menor.
 

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Pronto, agora tu tem a missão de fazer no final esse joguete rodar num celular com Android ou iOS.

:coolface

Pode usar o sensor de movimento para fazer a nave se mover da esquerda à direita.
Meu TCC está pronto!!!! :klolz

Não esqueça de concluir hein! :klol
 

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Fiquei uns dias foras porque tenho andado meio enrolado com obras em casa, trabalhos e aulas da pós, curso de francês e tal, mas antes disso cheguei a fazer a alteração no código e não mudou nada, continua não me deixando alterar os elementos do Parallax. Vou ver se consigo dar uma olhadinha no final de semana.

Edit: Funcionou!!!!!
Mas meu ScrollingParallax.cs ficou assim:

using UnityEngine;

[System.Serializable]

public class ScrollingParallax {

public float speed = 1;

public System.Collections.Generic.List<GameObject> images;
}
Ai apareceu a opção lá pra colocar as imagens no Parallax. Vou agora continuar de onde parei, enfim!

Edit 2: Acho que foi burrice minha agora vendo esse gif dessa parte.
 
Ultima Edição:

thiagoencd

Bam-bam-bam
Mensagens
165
Reações
181
Pontos
469
Enfim terminei essa parte!!!
Só uma coisa que aconteceu comigo, no script Player.cs, se eu coloco if(jump), ele me retorna um erro dizendo que não pode converter tipo float to bool, então resolvi colocando if(jump <= 0), funcionou, depois testei colocando if(jump == 1), já que o space está no positive button e também funcionou. Não sei se essa seria a melhor prática né, mas foi como consegui resolver.
 

j0kk3r

Mil pontos, LOL!
VIP
Mensagens
12.352
Reações
16.925
Pontos
1.284
@Landstalker , tô com um problema aqui, pode me dar uma ajuda?

Dei uma lida no seu material pra ver se me ajudava mas não ajudou =(. Comecei um projeto de brincadeira aqui pra testar e aprender umas coisas novas, mas um problema que tive no projeto anterior continua aparecendo nesse e eu não sei como resolver. Já procurei em tudo que é lugar, e vou continuar procurando, mas não consigo achar a solução.

Meu sprite tem ghost na hora que ele se move (óbvio né, parado é impossível huahuahua) com a camera parada. E inimigos que se movem na direção oposta da camera quando esta está se movendo também têm...

Achei uns posts no fórum da unity do pessoal reclamando disso, mas ninguém com uma solução que realmente funcione. Inclusive vi alguns usuários falando que isso não acontece com a Construct por exemplo. Comentaram sobre a taxa de atualização do monitor, mas já testei em monitores diferentes, já coloquei o joguinho no cel pra testar e sempre o resultado é o mesmo. Se eu pauso como dizem pra fazer, eu não vejo o ghost, mas ainda assim não acho que a culpa é do monitor, pq eu nunca reparei algum ghost em algum jogo 2d que eu tenha jogado nesse pc. Já tentei desativar o vsync, aumentar e diminuir os fps.. nada do que eu faço dá algum resultado. Pensei que poderia ser problema na animação, mas até com 1 frame fixo o ghost aparece quando me movo para os lados.

Faz alguma ideia do que mais eu posso fazer????

Ah, já testei algumas opções de controle diferente. Já movi o boneco de 200 formas diferentes, e sempre sai com ghost.
 

Landstalker

Lenda da internet
Mensagens
19.356
Reações
39.661
Pontos
1.584
@j0kk3r

Uma das duas coisas podem estar ocorrendo.

1 - A forma como você tá movendo os seus objetos em tela:

* Estude a padrão de comportamento, física, movimentação e enquadramento de objetos 2D na Unity, talvez algo simples como Interpolação esteja causando isso.

2 - O seu jogo 2D não está configurado para pixel perfect:

* Como a Unity é na verdade uma engine 3D adaptada para 2D, os jogos podem ficar com um gráficos estranhos, como se eles não estivessem "encaixados" ou com os pixels estourados ou borrados, isso se deve por causa da matriz de projetos que é formada. Engines completamente 2D já tratam isso de forma natural fazendo com que os objetos com suas dimensões e deslocamento sejam tratados diretamente na medida de pixel, na Unity, por sua vez, você não move diretamente pixels e sim, unidades, o que pode ser um valor completamente arbitrário (o default é de 100 pixels, mas pode variar).

Entendo isso, deve-se usar técnicas de Pixel Perfect na Unity que a própria comunidade já tinha desenvolvido várias solução ao longo dos anos. Mas a própria Unity resolver dar uma maior atenção a esse caso e tinha publicado uma solução aqui. No entanto, a partir da versão 2018.2 já está disponível por padrão na Unity sem você precisar pegar de terceiros, como a minha versão da Unity é mais antiga que essa, eu não pude testar a versão deles, mas acredito que esteja completamente OK.

--------------

Tente focar nesses dois pontos que eu citei, o da movimentação dos objetos (estude a diferença de FixedUpdate para Update, sendo o primeiro mais adequado para tratamento de física, estude smoothDeltaTime e o uso correto de DeltaTime, dar-se para limitar fps usando os times da Unity normalmente) e o ponto do pixel perfect que causa muita dor de cabeça para muita gente.

Qualquer coisa, tamos aí.
 

j0kk3r

Mil pontos, LOL!
VIP
Mensagens
12.352
Reações
16.925
Pontos
1.284
@j0kk3r

Uma das duas coisas podem estar ocorrendo.

1 - A forma como você tá movendo os seus objetos em tela:

* Estude a padrão de comportamento, física, movimentação e enquadramento de objetos 2D na Unity, talvez algo simples como Interpolação esteja causando isso.

2 - O seu jogo 2D não está configurado para pixel perfect:

* Como a Unity é na verdade uma engine 3D adaptada para 2D, os jogos podem ficar com um gráficos estranhos, como se eles não estivessem "encaixados" ou com os pixels estourados ou borrados, isso se deve por causa da matriz de projetos que é formada. Engines completamente 2D já tratam isso de forma natural fazendo com que os objetos com suas dimensões e deslocamento sejam tratados diretamente na medida de pixel, na Unity, por sua vez, você não move diretamente pixels e sim, unidades, o que pode ser um valor completamente arbitrário (o default é de 100 pixels, mas pode variar).

Entendo isso, deve-se usar técnicas de Pixel Perfect na Unity que a própria comunidade já tinha desenvolvido várias solução ao longo dos anos. Mas a própria Unity resolver dar uma maior atenção a esse caso e tinha publicado uma solução aqui. No entanto, a partir da versão 2018.2 já está disponível por padrão na Unity sem você precisar pegar de terceiros, como a minha versão da Unity é mais antiga que essa, eu não pude testar a versão deles, mas acredito que esteja completamente OK.

--------------

Tente focar nesses dois pontos que eu citei, o da movimentação dos objetos (estude a diferença de FixedUpdate para Update, sendo o primeiro mais adequado para tratamento de física, estude smoothDeltaTime e o uso correto de DeltaTime, dar-se para limitar fps usando os times da Unity normalmente) e o ponto do pixel perfect que causa muita dor de cabeça para muita gente.

Qualquer coisa, tamos aí.

1 - A forma como você tá movendo os seus objetos em tela:

== Tentei usar sem interpolação, com, e com aquela opção extrapolação. Mas vou dar uma atenção nisso pra tentar entender melhor.

2 - O seu jogo 2D não está configurado para pixel perfect:


== Eu vi a palavra pixel perfect em algum lugar na unity, e acho que habilitei isso... vou dar uma procurada aqui e ler esse artigo que você passou. Preciso entender melhor isso aqui

O movimento que eu tô fazendo e tá me incomodando com ghost é bem simples, na verdade só andar para o lado. Tentei usar com o update, o fixedupdate... tentei usar o delta time pra ver o que eu conseguia fazer, mas nada ajudou.

Preciso estudar esse pontos que você passou e entender melhor muita coisa ainda. Valeu pela resposta!

Edit:
Ah sim. Eu tentei mover o boneco usando velocity, AddForce.. mas sempre que chega uma velocidade razoável o ghost aparece.
 
Topo Fundo