Conectar-se
Quem está conectado
31 usuários online :: Nenhum usuário registrado, Nenhum Invisível e 31 Visitantes

Nenhum

Ver toda a lista


Compartilhe
Ver o tópico anteriorIr em baixoVer o tópico seguinte
avatar
Ranking : Sem avaliações
Data de inscrição : 16/01/2014
Número de Mensagens : 314
Insígnias de JAM :

Insignia 1x 0 Insignia 2x 1 Insignia 3x 0
Reputação : 31

Prêmios
   : 0
   : 1
   : 0
Ver perfil do usuáriohttp://google.com

Efeito luz e sombra [parte 1]

em Sex 07 Jul 2017, 22:39
Reputação da mensagem: 100% (2 votos)
Título: Efeito luz e sombra
Plataforma: Funcionou sem problemas em HTML5 e Android.
Dificuldade: Sem dificuldades Laughing
Pré requisitos: Conhecimento básico em Blending, surfaces, e um pouco de matemática Basketball

Olá pessoal do fórum, venho trazer uma série de tutoriais sobre efeito de luz e sombra. Nesse primeiro tutorial, separei para estudarmos mais a parte matemática do funcionamento. Creio que para alguns leitores esse tema é um tanto quanto complexo, mas não é nada disso, vou desenvolver esse tutorial de forma simples e eficiente para iniciantes no GameMaker. O resultado final será um efeito muito parecido com esse:

Introdução e Convenções
Essa solução de luz e sombra que eu desenvolvi funciona apenas para objetos "quadrados", pois eles possuem as extremidades bem definidas. Basicamente todo objeto que poderá interromper a luz armazenará informações da localização de suas bordas. Por exemplo, um objeto com uma Sprite quadrada (32x32), as extremidades serão os pontos (0,0), (32,0), (32,32), (0,32). Portanto, vamos estipular essa regra, todo objeto com capacidade de "fazer" sombra armazenará uma matriz contendo as informações do ponto de cada extremidade.

Desenvolvimento do algoritmo principal
Vamos começar a programar, crie um projeto e também 2 objetos, nomeie eles com "LightController" e "Block", o Block será capaz de interromper a luz, então como a gente já tinha combinado antes, vamos fazer uma matriz que contenha as informações de cada extremidade da Sprite desse objeto, se a sua Sprite estiver centralizada apenas altere o código:

Event Create Block -> Create:
Código:
edge[0,0] = x;
edge[0,1] = y;

edge[1,0] = x+sprite_width;
edge[1,1] = y;

edge[2,0] = x+sprite_width;
edge[2,1] = y+sprite_height;

edge[3,0] = x;
edge[3,1] = y+sprite_height;
//Edge será a matriz com 4 elementos contendo a extremidade das arestas, a segunda chave representa x se for 0, e y se for 1

Agora no objeto LightController vamos desenhar linhas partindo da posição do mouse até as extremidades dos blocos. Para isso, percorreremos por todas as instancias, e se caso uma delas for do tipo "Block", então o controlador irá desenhar a linha para cada extremidade do bloco.

Event Draw LightController -> Draw
Código:
for(i = 0; i < instance_count; i++){
    if(instance_id[i].object_index == Block){
        tmp = instance_id[i];
        for(j = 0; j < 4; j++){
            draw_line(x, y, tmp.edge[j,0], tmp.edge[j,1]);
        }
    }
}

//Percorrerá todas as instancias, e se caso a instancia for da classe Bloco, ira desenhar uma linha partindo da posição do mouse até as extremidades do bloco


Ao final desse passo, teremos uma noção do comportamento da sombra, ao compilar você terá um resultado muito parecido com a imagem acima, deve estar pensando que a direção das linhas estão erradas, e realmente estão. A sombra deverá ser repelida e não atraída pela fonte de luz (fonte de luz é a posição do mouse), ou seja, devemos inverter o sentido de cada linha, vamos modificar o evento draw.

Event Draw LightController -> Draw
Código:
for(i = 0; i < instance_count; i++){
    if(instance_id[i].object_index == Block){
        tmp = instance_id[i]; //variável temporária para instancia do Bloco.
        for(j = 0; j < 4; j++){ //Percorrerá as 4 extremidades do Bloco, j = 0 extremidade 0, e assim por diante.
            dir  = point_direction(mouse_x, mouse_y, tmp.edge[j,0], tmp.edge[j,1]); //pega a direção entre o mouse e a extremidade "j" do bloco;
            posx = tmp.edge[j,0]; //posx será nossa posição em relação a X da extremidade "j".
            posy = tmp.edge[j,1]; //...
            draw_line(posx, posy, posx + lengthdir_x(100, dir), posy + lengthdir_y(100, dir)); //Irá desenhar linhas partindo das extremidades, essas terão tamanho de 100 pixels e estarão na direção da variável "dir", usaremos lengtdir_x,y. Poderíamos ter usado seno e cosseno, mas não precisamos reinventar a roda.
        }
    }
}


Ao compilar terá um resultado parecido com a imagem acima, note que pintei de vermelho os vértices das linhas que não precisam ser desenhadas. Tem um truque simples para remover essas linhas indesejadas, repare que o par de linhas a desenhar possui um angulo maior que as outras, vou colocar uma imagem para você entender melhor.


Na imagem desenhei todos os ângulos, presta atenção que o par de linhas que definirão nossa sombra será o par que possui o maior ângulo, e nesse caso serão as linhas do ângulo vermelho. No próximo passo irei novamente modificar o evento "draw" do LightController, iremos calcular o maior angulo e apenas duas linhas serão desenhadas e representarão nossa sombra.

Event Draw LightController -> Draw
Código:
for(i = 0; i < instance_count; i++){
    if(instance_id[i].object_index == Block){
        tmp = instance_id[i];
        linha[0,0] = 0; //Matriz que armazenara as posições x,y e a direção. A primeira chave indica qual linha estamos manipulando, no nosso caso 2 linhas a 0 e 1. Na segunda chave 0 é o ponto X da extremidade, 1 é o ponto y, e 2 é o angulo entre a extremidade e o mouse.
        
        maxAngle = 0; //Antes de entrar no looping com 2 fors, setaremos o máximo angulo como 0.
        for(l = 0; l < 4; l++){
            for(j = 0; j < 4; j++){ //Os 2 fors combinarão todas as nossas extremidades, e iremos pegar a que tiver o maior angulo entre si.
                if(i != j){ //Não iremos comparar a mesma extremidade..
                    dirLinha1 = point_direction(mouse_x, mouse_y, tmp.edge[l,0], tmp.edge[l,1]); //Direção da extremidade "l" em relação a posição do mouse
                    dirLinha2 = point_direction(mouse_x, mouse_y, tmp.edge[j,0], tmp.edge[j,1]); //Direção da extremidade "j" em relação a posição do mouse
                    ang = abs(angle_difference(dirLinha1, dirLinha2)); //calcula o angulo entre a linha1 e a linha2
                    
                    if(l == 0 || abs(ang) > maxAngle){ //Se caso o angulo atual for maior maxAngle, então devemos atualizar o par de linhas a serem escolhidas
                        linha[0,0] = tmp.edge[l, 0];
                        linha[0,1] = tmp.edge[l, 1];
                        linha[0,2] = dirLinha1;
                        //A nova linha escolhida partirá da extremidade "l"

                        linha[1,0] = tmp.edge[j, 0];
                        linha[1,1] = tmp.edge[j, 1];
                        linha[1,2] = dirLinha2;
                        //A outra nova linha escolhida partirá da extremidade "j"
                        
                        maxAngle = ang; //Atualiza o maxAngle para o angulo atual
                    }
                }
            }
        }
        
        draw_line(linha[0,0], linha[0,1], linha[0,0] + lengthdir_x(100, linha[0,2]), linha[0,1] + lengthdir_y(100, linha[0,2]));  //Desenha a linha 0
        draw_line(linha[1,0], linha[1,1], linha[1,0] + lengthdir_x(100, linha[1,2]), linha[1,1] + lengthdir_y(100, linha[1,2]));  //Desenha a linha 1

    }
}

No código, a matriz "linha" armazenará as informações das extremidades selecionadas. A primeira chave indica qual linha estou manipulando, e a segunda chave caso for: 0 é a posição X da linha, 1 posição Y da linha, 2 é o ângulo entre a posição da extremidade e a posição mouse.


Ao compilar você terá algo parecido com a imagem acima, agora as duas linhas desenhadas representam corretamente nossa sombra. Brinque um pouco e veja que as linhas se alteram de acordo com a posição do mouse do modo que queríamos que fosse.

Desenhando formas primitivas no lugar das linhas  Cool
As linhas deram um efeito bacana, mas ainda não é o que procuramos. A melhor maneira de representar a sombra seria usando uma trapézio, sabemos que no GameMaker podemos desenhar várias formas, por exemplo, círculos, quadrados, e triângulos. Entretanto nenhum se encaixa para o nosso formato, uma saída que encontrei para isso seria utilizar as formas primitivas. Basicamente, para desenhar uma forma primitiva passamos uma sequência de pontos e o GameMaker magicamente une os pontos formando assim um polígono. Aconselho a dar uma olhada no help sobre o tema, 2D primitives. Vamos para última alteração do código!

Event Draw LightController -> Draw
Código:
for(i = 0; i < instance_count; i++){
    if(instance_id[i].object_index == Block){
        tmp = instance_id[i];
        linha[0,0] = 0;
        maxAngle = 0;
        for(l = 0; l < 4; l++){
            for(j = 0; j < 4; j++){
                if(i != j){
                    dirLinha1 = point_direction(mouse_x, mouse_y, tmp.edge[l,0], tmp.edge[l,1]);
                    dirLinha2 = point_direction(mouse_x, mouse_y, tmp.edge[j,0], tmp.edge[j,1]);
                    ang = abs(angle_difference(dirLinha1, dirLinha2));
                    
                    if(i == 0 || abs(ang) > maxAngle){
                        linha[0,0] = tmp.edge[l, 0];
                        linha[0,1] = tmp.edge[l, 1];
                        linha[0,2] = dirLinha1;
                        
                        linha[1,0] = tmp.edge[j, 0];
                        linha[1,1] = tmp.edge[j, 1];
                        linha[1,2] = dirLinha2;
                        maxAngle = ang;
                    }
                }
            }
        }
        
        //draw_line(linha[0,0], linha[0,1], linha[0,0] + lengthdir_x(100, linha[0,2]), linha[0,1] + lengthdir_y(100, linha[0,2]));
        //draw_line(linha[1,0], linha[1,1], linha[1,0] + lengthdir_x(100, linha[1,2]), linha[1,1] + lengthdir_y(100, linha[1,2]));
        //Comentei as linhas pois agora usaremos primitivas \o/
        
        draw_set_color(c_black);
        draw_set_alpha(1);
        //Só definindo a opacidade e a cor da sombra.

        draw_primitive_begin(pr_trianglestrip); //Começa a desenhar uma primitiva, antes do comando draw_primitive_end() passamos uma lista de pontos, triangleship é o tipo de preenchimento (vá no help do gamemaker para ver os outros tipos).

        //Passamos então a nossa lista de pontos
        draw_vertex(linha[0,0], linha[0,1]);
        draw_vertex(linha[0,0] + lengthdir_x(6000, linha[0,2]), linha[0,1] + lengthdir_y(6000, linha[0,2])); //6000 é um valor bem alto, pois queremos garantir que a sombra seja maior que o tamanho da Room.
        draw_vertex(linha[1,0] + lengthdir_x(6000, linha[1,2]), linha[1,1] + lengthdir_y(6000, linha[1,2]));
        draw_vertex(linha[1,0], linha[1,1]);
        draw_vertex(linha[0,0], linha[0,1]);

        draw_primitive_end(); //Após passar a lista de pontos chamamos a função para renderizar a primitiva.
    }
}

O resultado final será algo como a imagem acima. Repare que todos as sombras devem ser maior que o tamanho da room.

Conclusão
Finalizamos a parte 1, acho que a mais difícil (pra tu ver como é fácil, a mais difícil já acabou cheers). No próximo tutorial vou trazer um pouco de Blending de superfícies, isso vai dar um toque bem interessante na sombra. É isso, qualquer dúvida, ou crítica é só deixar nos comentários. Vlw.


Última edição por Lighter em Seg 10 Jul 2017, 05:07, editado 1 vez(es)
avatar
Ranking : Sem avaliações
Data de inscrição : 21/11/2010
Número de Mensagens : 775
Insígnias de JAM :

Insignia 1x 0 Insignia 2x 0 Insignia 3x 0
Reputação : 25

Prêmios
   : 0
   : 0
   : 0
Ver perfil do usuário

Re: Efeito luz e sombra [parte 1]

em Dom 09 Jul 2017, 00:56
Wow, curti muito o tutorial, parabéns.

Achei muito bem explicado, eu sempre tive um pouco de dificuldade de entender como estas sombras eram projetadas.

Fico no aguardo do próximo tuto, também nunca sei usar surfaces kkk

+1

Vlw
avatar
Ranking : Sem avaliações
Data de inscrição : 16/01/2014
Número de Mensagens : 314
Insígnias de JAM :

Insignia 1x 0 Insignia 2x 1 Insignia 3x 0
Reputação : 31

Prêmios
   : 0
   : 1
   : 0
Ver perfil do usuáriohttp://google.com

Re: Efeito luz e sombra [parte 1]

em Seg 10 Jul 2017, 04:58
Tedi Ripper escreveu:Wow, curti muito o tutorial, parabéns.

Achei muito bem explicado, eu sempre tive um pouco de dificuldade de entender como estas sombras eram projetadas.

Fico no aguardo do próximo tuto, também nunca sei usar surfaces kkk

+1

Vlw

Vlw tedi. Legal que deu de entender, achei que expliquei mal ou enrolei em algumas partes (T.T). Vou tentar trazer a segunda parte no final de semana. E realmente, esse efeito parece ser um sistema bem complexo de implementar, mas é só uma projeção entre 2 linhas XD.
Ver o tópico anteriorVoltar ao TopoVer o tópico seguinte
Permissão deste fórum:
Você não pode responder aos tópicos neste fórum