Nome: Lasers simples
Blogueiro: Saim
Descrissao: Esse tutorial mostra uma maneira de lidar com lasers. Não é parte do escopo desse tutorial ensinar a desenhar o laser, embora seja mostrada uma forma de se fazer isso.
Versao: Lite e Pro
Foto(s): [NAO TEM]
Tutorial:
Introdução
Você quer fazer um raio laser. Primeiro, vou deixar aqui a minha definição de raio laser em games.
Saim, sua definição é diferente da minha! Eu quero que meu laser vá até o infinito!Raio laser é uma linha que parte de um ponto e vai até outro ponto e é interrompida quando colide com um objeto, no ponto em que ocorre essa colisão, podendo ou não alterar alguma variável do objeto colidido
Azar o seu. Eu só sei fazer de um ponto a outro. Normalmente é possível substituir "até o infinito" por "muito longe". Espero que sirva.
Uma vez que se defina esses dois pontos, verifica-se se existe algum objeto entre eles. Se houver, defina o objeto como "o objeto que o laser acertou" e defina o ponto final como o ponto imediatamente antes da colisão ocorrer. Repita esse procedimento até que não exista nenhum objeto entre o ponto inicial e o (novo) ponto final. Faça o que tiver que fazer com o objeto colidido e desenhe o laser de um ponto a outro.
Fim
Como fazer isso
Ah, você já sabia que o tutorial não acabava ali? Droga, eu queria fazer surpresa...
Bom, tem duas perguntas principais, aqui.
Como saber se há um objeto colidindo com a linha?
Como saber o ponto imediatamente anterior à colisão?
Pra primeira pergunta, existe uma função específica no game maker. É a collision_line. Ela te retorna a id da instância que colide com a linha definida pelos pontos (que o usuário define nos argumentos). Não havendo nenhuma instância, ela retorna o objeto especial noone. Deixa eu te apresentar os argumentos dela:
Assim, jogamos os pontos do laser nos argumentos da função e pronto, não apenas sabemos se alguém colide com a linha como também sabemos quem é.collision_line(x1, y1, x2, y2, obj, prec, notme)
x1,y1,x2,y2: Os valores de x e y dos pontos que definem a linha
obj: O objeto a ser verificada a colisão. Pode ser um objeto, um parent ou uma id.
prec: Se a colisão é precisa (true) ou não (false).
notme: Se o objeto que chama a função deve ser ignorado (true) ou levado em conta (false)
A segunda pergunta só faz sentido se houver alguma colisão. Aí, a coisa complica um pouco. Se o laser for prefeitamente ortogonal, podemos simplificar usando os valores de bbox_(left, right, top, bottom) da instância colidida. Mas e se não for? Só temos a id da instância colidida e, por consequencia, sua posição (que, conforme veremos, não será utilizada). O que faremos é ir testando novos comprimentos de laser até chegar naquele que NÃO colide com nada, mas que se aumentar um pouquinho só, já colide. Isso significa ir tentando uma aproximação, ir reduzindo uma variação até um valor de precisão pré-definido. Quando a variação for menor que a precisão, saberemos que encontramos o ponto.
Podemos definir essa variação como um pixel, por exemplo, ou um valor que o jogador não vai notar que o ponto não está exato.
Existem diversas formas de se aproximar, mas a que me parece mais rápida é também a mais simples: soma-se ou subtrai-se (conforme a colisão aconteça ou não) um valor variável, que começa sendo a metade do comprimento do laser e vai sendo dividido por 2. Com poucas iterações reduz-se esse valor a menos que um. Obviamente, quanto maior o valor inicial, mais iterações serão necessárias, o que pesa no processamento.
- Melhore esse tutorial:
Note que seria possível já entrar direto na rotina, ignorando o passo 0, mas acho mais eficiente entrarmos nela apenas nos casos em que ela for necessária. Afinal, mesmo que se chegue no resultado com poucas iterações, podemos ter muitos lasers operando ao mesmo tempo.0- Verifica-se se é necessária alguma iteração. Se não, desenha-se o laser e fim. Se sim, continua-se com a rotina.
1- Divide-se o comprimento do laser pela metade e define-se o valor inicial da variável a ser somada/subtraída ao comprimento do laser como o novo comprimento total do laser.
2- Verifica-se se há colisão do laser com o objeto a ser colidido (já que o comprimento do laser mudou). Divide-se o valor da variável por 2.
3- Se houve colisão, subtrai-se do comprimento total do laser, o valor da variável. Se não, soma-se esse valor.
4- Verifica-se se o valor da variável ficou menor que a precisão requerida.
5- Se sim, podemos parar com o procedimento, já chegamos à precisão requerida. Se não, voltamos ao passo 2.
Note, também, que havendo mais de uma instância colidida, essa rotina encontrará aquela mais próxima do início do laser.
No passo 2, podemos também armazenar a id colidida em uma outra variável pra alterarmos alguma variável dela, como a vida, por exemplo.
Fim (mesmo) da teoria.
Script
O que eu vou apresentar a seguir é um script que não retorna nada, mas define o valor de algumas variáveis. Ele não retorna nada porque eu precisaria de 3 retornos: o valor em x do fim do laser, o valor em y do fim do laser e a id da instância colidida.
Então, se você quiser usar outros valores de variáveis, fique à vontade pra alterar o script.
- Código:
/* uso do script:
** laser(x1, y1, x2, y2, obj, precisão)
** o script verifica se há colisão do laser com uma instancia do objeto "obj".
** havendo, ele aproxima o laser até o ponto de colisão com o objeto
** a id colidida é armazenada na variável "vitima"
** o ponto de colisão é armazenado nas variáveis "xL" e "yL"
*/
var x1, y1, alvo;
x1 = argument0; y1 = argument1; //posição inicial do laser
xL = argument2; yL = argument3; //posição final do laser
alvo = argument4; //quem o laser vai procurar
//primeiro, verifica-se se o script é mesmo necessário
vitima = collision_line(x1, y1, xL, yL, alvo, 1, 1);
if (vitima == noone){ //se não há colisão
exit; //acaba com o script aqui mesmo
}
//Se ainda estamos aqui, é porque HOUVE a colisão. Precisaremos de mais algumas variáveis.
var prec, comp, ang, soma, novaVitima;
prec = argument5;
comp = point_distance(x1, y1, xL, yL) / 2; //tamanho do laser, já pela metade pra acelerar o processo
ang = point_direction(x1, y1, xL, yL); //ângulo do laser
soma = comp; //valor a ser somado/subtraído no tamanho do laser, até achar o ponto
while (soma >= prec){
xL = x1 + lengthdir_x(comp, ang); yL = y1 + lengthdir_y(comp, ang);
novaVitima = collision_line(x1, y1, xL, yL, alvo, 1, 1);
soma *= 0.5; //diminui o tamanho a ser somado/subtraído
if (novaVitima == noone){ //se o comprimento atual é menor do que o que dá colisão
comp += soma; //aumenta o comprimento
}
else { //se o comprimento atual é maior ou igual ao que dá colisão
comp -= soma; //diminui o comprimento
vitima = novaVitima; //atualiza a instância mais próxima (podendo repetir o valor)
}
}
Não acabou ainda???
Bom, isso nos dá o ponto final pra desenharmos o laser e a instância que o laser colide. O que mais precisamos? Precisamos achar uma utilidade pra isso!
A seguir, alguns exemplos.
Desenhar o laser como uma linha
Coloque, no draw event:
- Código:
draw_line(x1, y1, xL, yL);
Acertar o objPlayer, diminuir sua vida e permitir que ele se esconda atrás do objParede
Coloque, no step event
- Código:
//sendo que x2, y2 é o ponto máximo do laser:
xL = x2; yL = y2;
laser(x1, y1, xL, yL, objParede, 1);
//agora, (xL, yL) é o ponto de colisão com a parede (se houver colisão) ou o ponto máximo, se não houver colisão
laser(x1, y1, xL, yL, objPlayer, 1);
//agora, (xL, yL) é o ponto de colisão com o player (se houver colisão) e não temos certeza do valor de "vitima"
if (vitima != noone){ //se há uma vítima
if (vitima. object_index == objPlayer){ //se a vítima for o player (ou algum personagem que se machuque)
vitima. vida -= 1; //faz algo com a vítima
}
}
Uma outra forma de verificar isso seria verificar a colisão diretamente no player e, depois, usar só um collision_line pras paredes, mas aí, você não teria o ponto de colisão na parede. Além do mais, espera-se que seja mais comum o laser acertar paredes do que acertar o player.
Uma bala rápida como uma bala
Jogos de tiro não são muito realistas porque normalmente é possível var a bala, sendo que na vida real, só com uma câmera muito rápida. Se você usar o raciocínio acima no evento de atirar (ao invés de usar o step) e NÃO desenhar o laser, o efeito será o de uma bala atingindo o alvo instantaneamente. O resto do realismo fica por sua conta. Você ainda pode usar o ponto (xL, yL) pra criar um efeito de tijolos sendo estilhaçados ou sangue, pra mostrar pro jogador o ponto que ele atingiu.
laser articulado
Você ainda pode fazer o laser rodar, ficar num canhão que se move, oscilar entra "atirando" e "sem atirar", etc, usando sua criatividade. Mude o valor das coordenadas do ponto final pra fazer o laser mudar de direção. Faça o ponto inicial depender da posição do objeto e você pode prender o laser num canhão que se move. Rode o script apenas quando uma variável for verdadeira e prenda essa variável num alarm e - voilà, você tem um laser intermitente. Use tudo isso num monte de objetos e ao invés de tirar a vida do player, acione um som ao colidir o laser e - pimba! - um jogo de espionagem.
Brinque bastante e, se descobrir novas utilidades para os lasers, comente aqui!
Abraços,
saim
Update!!! (22/12/2011)
Com a dica do Pedrø (logo abaixo), é possível reduzir drasticamente o comprimento inicial do laser e ainda mais drasticamente o tamanho da veriável que será reduzida até o valor da precisão, o que reduz drasticamente o numero de iterações e, consequentemente, aumenta a eficiência do script. Alterei uma coisinha aqui e outra ali, o script ficou com essa cara:
- Código:
/* uso do script:
** laser(x1, y1, x2, y2, obj, precisão)
** o script verifica se há colisão do laser com uma instancia do objeto "obj".
** havendo, ele aproxima o laser até o ponto de colisão com o objeto
** a id colidida é armazenada na variável "vitima"
** o ponto de colisão é armazenado nas variáveis "xL" e "yL"
*/
var x1, y1, alvo;
x1 = argument0; y1 = argument1; //posição inicial do laser
xL = argument2; yL = argument3; //posição final do laser
alvo = argument4; //quem o laser vai procurar (pode ser um objeto, um parent ou uma id)
//primeiro, verifica-se se o script é mesmo necessário
vitima = collision_line(x1, y1, xL, yL, alvo, 1, 1);
if (vitima == noone){ //se não há colisão
exit; //acaba com o script aqui mesmo
}
// Se ainda estamos aqui, é porque HOUVE a colisão. Precisaremos de mais algumas variáveis.
// Mas antes, veremos qual é a vítima mais próxima e usaremos a distância até ela
// pra reduzir o número de iterações.
var vitPrio, prec, comp, ang, soma, novaVitima;
vitPrio = ds_priority_create(); //cria uma lista de prioridades
with(alvo){ //para todas as instâncias de "alvo"
if (collision_line(x1, y1, other . xL, other . yL, id, 1, 0)) { //se está no caminho do laser
//entra na lista, com a prioridade sendo a distância até o ponto de origem do laser
ds_priority_add(vitPrio, id, point_distance(x, y, x1, y1));
}
}
vitima = ds_priority_find_min(vitPrio); //vitima passa a ser a instância mais próxima do laser
ds_priority_destroy(vitPrio); //me livro da lista, liberando memória
prec = argument5;
comp = point_distance(x1, y1, vitima . x, vitima . y); //tamanho do laser
ang = point_direction(x1, y1, xL, yL); //ângulo do laser
soma = point_distance(0, 0, vitima . sprite_width, vitima . sprite_height); //valor a ser somado/subtraído no tamanho do laser, até achar o ponto
while (soma >= prec){
xL = x1 + lengthdir_x(comp, ang); yL = y1 + lengthdir_y(comp, ang);
novaVitima = collision_line(x1, y1, xL, yL, alvo, 1, 1);
soma *= 0.5; //diminui o tamanho a ser somado/subtraído
if (novaVitima == noone){ //se o comprimento atual é menor do que o que dá colisão
comp += soma; //aumenta o comprimento
}
else { //se o comprimento atual é maior ou igual ao que dá colisão
comp -= soma; //diminui o comprimento
vitima = novaVitima; //só é util pra alguns casos muito específicos
}
}
Nenhum comentário:
Postar um comentário