Avatar do usuário
Tutoriais & Aulas
Colaborador
Colaborador
Autor
Mensagens: 174
Registrado em: Qui Abr 28, 2016 11:37 am
Karma: 240

[GRAF] [Tutorial] Surfaces

Qui Abr 28, 2016 5:45 pm

Autor original: saim

Nome: Dicas a respeito de surfaces
Descrição: Aqui, vamos discutir como usar surfaces
Nível de dificuldade: Não sei. Me ajudem aí, nos comentários. Acho que é intermediário, mas tem coisas bem básicas.
Requerimentos: GM Pro, exemplos feitos com a versão 8


surfaces: Que bicho de 7 cabeças é esse tal de surfaces? Bicho nenhum. Surface é uma tela, só isso. Pense num paint brush, só que, ao invés de desenhar com o mouse, você vai desenhar com o teclado.
Você cria uma surface usando a função "surface_create(largura, altura)". Pronto, você criou uma surface. Ah, essa função te retorna a id da surface pra você usar depois, em outras funções, portanto armazene essa id numa variável. Vou chamar ela de "Surf", com "S" maiúsculo pro GM não sugerir autocompletar. 
"Surf=surface_create(largura, altura)".

Uma tela serve pra desenhar. Só que o GM, por padrão, desenha na tela que é mostrada durante o jogo (sim, ela também é uma surface, mas é outra história). Pra desenhar na SUA surface, você deve explicar ao GM o que você quer. A função que estou falando é "surface_set_target(id)". No nosso caso, a variável que armazenou a id da surface é "Surf", ou seja: 
surface_set_target(Surf)

Sabe o que tem nela, quando ela criada? Eu também não. Uma vez mandei desenhar uma surface que tinha acabado de ser criada, sem colocar nada nela e o que eu vi, não foi bonito. Portanto, vamos garantir que ela seja uma tela totalmente lisa, antes de começar a trabalhar. Tem as funções "draw_clear(cor)" e "draw_clear_alpha(cor, opacidade)". Eu gosto de usar draw_clear_alpha(0,0). Essas funções servem pra limpar a surface de cima abaixo (e elas podem ser usadas na tela do jogo, também). Porque eu usei a cor "0"? Resposta no tutorial de cores [strike](num futuro perto da sua casa!)[/strike].
Ok, criamos, definimos que é nela que vamos desenhar e pintamos ela toda de preto. E agora? Agora, desenhe pra valer. Todas as funções de draw funcionam na surface, esteja você no evento em que estiver (sim, você pode desenhar fora do draw event).
Vai lá, desenha. Você pode desenhar sprites, linhas, triângulos (dá pra fazer qualquer coisa com um número suficiente de triângulos), círculos, outras surfaces, a própria surface, pode desenhar a tela, pode fazer o que quiser, desde que com código. Se você for bom de serviço, dá até pra usar uma interface com o usuário pra desenhar, mas isso foge do nosso assunto.
Desenhou pra valer? Ótimo. Agora chega. Depois você vai ao banheiro, estamos quase acabando, aqui.
Agora precisamos guardar essa tela. Em outras palavras, parar de desenhar na sua surface e voltar a desenhar na surface built-in que é a própria tela do jogo.
surface_reset_target()

Quer VER sua surface? Vá no draw_event e coloque:
draw_surface(Surf, x, y)

Fim.

...

"Só isso?"
Bom, eu achei que já tava bom, mas já que é assim, vamos extender um pouco mais. Você está pensando "por que fazer a surface se eu posso fazer isso tudo direto na tela?", não é mesmo? Ok, deixa eu dar alguns exemplos do que se pode fazer com surfaces.

pausa - Quando você desativa todas as intâncias (instance_deactivate_all(1)), desativa todos os eventos delas, inclusive o evento de draw. Isso faz elas sumirem. Como fazer uma pausa que mostre a tela parada, sem ter que alterar o código de um-por-um dos objetos? Desative as instâncias mesmo mas, antes disso, crie uma surface com o evento de draw de todas elas. pra chamar o evento de draw de todas elas, não precisa usar with(all){event_perform(ev_draw, 0)}. Tem uma função ótima pra isso, "screen_redraw()". Crie a surface, desenhe tudo com screen_redraw(), desative todas as instâncias, desenhe a surface no draw event. Não, sério, é simples mesmo, olha só, coloque isso no obj_pausa:
evento de pausar
if !pausa{
 Surf=surface_create(room_width, room_height)
 surface_set_target(Surf)
 screen_redraw()
 surface_reset_target()
 instance_desctivte_all(1)
 }
 else{
 instance_activate_all
 surface_free(Surf)   //apaga a surface, liberando memória
 }

e, no draw event,
if pausa
 draw_surface(Surf, 0, 0)


personalização de sprites - Suponha que você esteja criando um RPG e queira que ao usar um capacete, por exemplo, o desenho do personagem mude pra um personagem com capacete. Primeiro você cria us sprites do personagem andando pra todo lado e outros do capacete, na altura da cabeça do personagem, andando pros mesmos lados, certo? Certo. Aí, no draw event, você coloca a imagem adequada do personagem e, por cima, a imagem adequada do capacete, certo? ERRADO. Na hora de adquirir o capacete, você cria toda uma série de sprites do personagem COM o capacete e desenha os novos sprites. É simples, olha:
var i, Surf, xop, yop, xoc, yoc;
//armazena a origem dos sprites
xop=sprite_get_xoffset(spr_personagem)
yop=sprite_get_yoffset(spr_personagem)
xoc=sprite_get_xoffset(spr_capacete)
yoc=sprite_get_yoffset(spr_capacete)
Surf=surface_create(largura, altura)//largura e altura do sprite final
for(i=0; i<numero_de_imagens; i+=1){
 draw_clear_alpha(0, 0)                   //aqui, você realmente quer eliminar o fundo
 draw_sprite(spr_personagem, i, xop, yop) //desenha o personagem, sem nada
 draw_sprite(spr_capacete,   i, xoc, yoc) //desenha o capacete, por cima
 if i=0{                                  //só pro primeiro índice
 spr_vestido=sprite_create_from_surface(Surf, 0, 0, largura, altura, 0, 0, xop, yop,) //cria o sprite
 }
 else{ //se não é o primeiro índice, o sprite já existe
 sprite_add_from_surface(spr_vestido, Surf, 0, 0, largura, altura, 0, 0) //só adicionar a imagem
 }
 }
surface_reset_target()//desliga a surface
surface_free(Surf)    //não preciso mais da surface, elimino-a pra economizar memória.

Muita coisa pra um evento só? Talvez. Mas isso vai economizar processamento durante o jogo, que é quando realmente importa. Sim, isso é simples! É só um amontoado de linhas, mas fácil de entender.

screenshots - Quer publicar seu jogo e precisa de uma screenshot? Fácil! Em algum evento, crie uma surface do tamanho da room, desenhe a room e grave essa surface num arquivo de imagem! Olha só:
var Surf;
Surf=surface_create(room_width, room_height)
surface_set_target(Surf)
draw_clear_alpha(0, 0)
screen_redraw()
surface_reset_target()
surface_save(Surf, "screenshot.png") //GM8
surface_free(Surf)


efeitos especiais - Bom, isso é mais complicado e eu não sei criar um exemplo, mas sabe aqueles efeitos que distorcem a tela toda e/ou parte dela, tipo uma lente de aumento... Ah, uma lente de aumento eu sei criar! Você desenha a tela toda numa surface e, em outra menor, desenha essa primeira surface, mas deslocada, de modo que a parte a ser aumentada encaixe direitinho na janela menor. Elimine a primeira e, no draw event, desenhe draw_surface_stretched. Pra conseguir uma lente circular, recorte, na surface menor, a parte que sobrar. "Ah, tá, e pra recortar, como faz?" Cara, isso é simples, só que é complicado. Fica pro tutorial de blend modes, [strike]num futuro um pouco mais distante que o de cores[/strike]. Resumindo, você cria uma surface "faca" desenhando um quadrado todo branco e subtraindo (bm_subtract) um círculo branco dele. Depois, você subtrai essa surface toda-branca-com-buraco-redondo da surface-lupa, deixando só o que estiver no buraco e voilá. Imagine o que dá pra fazer com primitives e textures!
(na verdade, pode haver um problema em desenhar, no draw_event, uma surface que é criada usando screen_draw, porque você vai desenhar a própria surface. Mas isso não gera nenhum buraco no universo, só uma surface estranha).

Bom, é isso! Se eu não fui claro em algum aspecto ou exemplo, me avisem.
Abraço! Te vejo no tutorial de cores!

Edit: Lembra que eu falei que não é uma boa idéia desenhar a tela numa surface que será desenhada na mesma tela? Mantenho o que disse, mas fiquei com consciência pesada. Que tutorial é esse que fala do problema e não resolve?
[strike]Bom, acho que não preciso explicar porque não é uma boa idéia fazer isso.[/strike] Isso é uma má idéia porque a própria surface vai ser desenhada sobre ela mesma e, quando você faz isso, normalmente que fazer uma distorção na imagem, então essa distorção acaba sendo aumentada exponencialmente.
Mas o que fazer, então? Não sei. Sério, não sei mesmo. Mas tem um truque que pode ser interessante.
Tem outra coisa que já falei que pode ser uma boa dar uma relembrada. A tela que é mostrada durante o jogo também é uma surface. Você não precisa mandar desenhar nela porque o game maker está programado pra fazer isso. Mas você pode mandar parar de fazer. A função pra isso é
set_automatic_draw(false)

Isso faz a tela parar de ser desenhada.
Calma, não quer dizer que o jogo vai ficar sem imagem nenhuma. Quer dizer que, ao chegar no final do draw_event, o programa vai parar de desenhar a surface da tela por cima de tudo o que já tiver sido desenhado. Ou seja, vai passar a ser possível desenhar em qualquer evento. Como o screen_redraw só executa os eventos de draw e você NÃO vai desenhar essa surface no draw, ela não vai ser re-desenhada. Claro que isso pode gerar uma série de problemas (que eu não consigo imaginar agora), mas se for usado com cuidado, pode ficar bacana.
Olha como ficaria uma lupa:
create:
//primeiro, faz a faca
faca=surface_create(50, 50)      //os valores, você altera a gosto
surface_set_target(faca)         //passa a desenhar na surface
draw_clear_alpha(c_white, 1)     //deixa a surface toda branca
draw_set_blend_mode(bm_subtract) //passa a apagar, ao invés de desenhar
draw_circle_color(25, 25, 25, $ffffff, $ffffff, 0) //apaga um círculo no centro da surface
draw_set_blend_mode(bm_normal)   //volta o blend_mode ao normal
surface_reset_target()           //larga a surface quieta

step:
if mouse_check_button(mb_left){ //se o mouse está pressionado
 set_automatic_draw(false) //para de redesenhar a tela
 var Surf;                 //cria uma variável pra redesenhar tela
 Surf=surface_create(room_width, room_height) //cria a surface que redesenhará a tela
 surface_set_target(Surf)  //passa a desenhar na surface
 draw_clear_alpha(0, 0)    //apaga tudo
 screen_redraw()           //desenha tudo
 surface_reset_target()    //para de desenhar na surface
 
 lupa=surface_create(50, 50)      //tem que ser do tamanho da faca
 surface_set_target(lupa)         //passa a desenhar na surface
 draw_surface(Surf, -mouse_x, -mouse_y) //desenha a outra surface, adequadamenteposicionada
 draw_set_blend_mode(bm_subtract) //passa a apagar ao invés de desenhar
 draw_surface(sf_faca, 0, 0)      //recorta, usando a faca
 draw_set_blend_mode(bm_normal)   //volta a desenhar ao invés de apagar
 surface_reset_target()           //para de desenhar na surface
 
 draw_surface(Surf, 0, 0)                                 //desenha a surface com a tela
 draw_surface_stretched(lupa, mouse_x, mouse_y, 100, 100) //desenha a surface com a lupa, esticada
 
 surface_free(Surf) //elimina a surface da tela
 }else //se o mouse está solte
 set_automatic_draw(true) //mantém a tela sendo desenhada normalmente

Gente, MUITO JUÍZO ao fazer isso. Supondo que o objeto possa ser deletado em algum momento, lembre-se sempre de colocar o blend_mode de volta ao normal e o set_automatic_draw de volta a true no destroy event.

Tags:

Quem está online

Usuários navegando neste fórum: Nenhum usuário registrado e 3 visitantes