Página 1 de 1

Seleção Cíclica (Cycle Select)

Enviado: Qua Nov 14, 2018 1:12 am
por leahnnovash
Nome: Seleção Cíclica
Ferramenta e Versão: Game Maker 2

A engine Game Maker oferece duas opções nativas para selecionar um objeto. Você pode selecionar o mais próximo (instance_nearest), ou você pode selecionar o mais distante (instance_furthest). Essas duas opções, embora cubram uma vasta variedade de casos, são insuficientes para situações mais específicas. O que acontece se você quiser o nth mais próximo? Pesquisando na Internet eu encontrei várias soluções nada elegantes para o problema, a vasta maioria delas sugerindo que você mova os n-1 objetos mais próximos para um lugar distante fora do mapa e use a seleção do mais próximo. Esse tipo de solução gera complexidade desnecessária já que você precisa armazenar todos os lugares originais dos objetos e depois restaurá-los as suas posições originais em um passo posterior. Como eu disse, nada elegante.

Segue a minha solução, que eu irei comentar linha a linha.

Sendo um script de GML, ele recebe os argumentos na forma de um array de argumentos referenciados por variaveis pré-definidas no estilo argument(x) onde x é o número do argumento começando em 0.

No script abaixo, argument0 é self, e argument1 é o tipo de objeto que você quer selecionar. Em geral, será algo do tipo obj_enemy, mas poderão haver circunstâncias onde você pode querer ser mais específico.

Código: Selecionar todos

var instance = instance_nearest(argument0.x, argument0.y, argument1);
var character_current_target = argument0.character_target;
As primeiras coisas a se fazer são saber quem está selecionado agora. Afinal, você está fazendo uma seleção cíclica então você precisa saber quem está selecionado para pegar o próximo do ciclo. E então pegar o primeiro da fila já que a engine só te deixa pegar o primeiro sem código adicional.

Código: Selecionar todos

do
{
	while (character_current_target != instance && character_current_target != noone)
	{
		instance_deactivate_object(instance);
		instance = instance_nearest(argument0.x, argument0.y, argument1);	
	}
Tendo o objeto inicial, eu inicio um loop para alcançar o objeto selecionado atual. Observe que ao invés de mover o objeto para fora da tela, eu simplesmente o desativo. A função de desativar um objeto é muito rápida mas em geral ela não é recomendada porque várias coisas deixam de acontecer se um objeto for desativado, principalmente os eventos dele, o que pode ocasionar um comportamento imprevisivel no jogo. Ainda assim, como esse script é atômico, rodando em apenas um único frame, em questão de microsegundos (tempo = 0) a não ser que o seu jogo possua uma quantidade imensa de interações complexas e simultâneas, o objeto é garantido de ser reativado quase que instantaneamente, sem causar qualquer problema.

O loop possui uma segunda condição que testa se o objeto selecionado atual é noone, ou seja, ninguém está selecionado, o que torna o loop para procura-lo desnecessario.

Código: Selecionar todos

	//the current target was found, we disable it and get the next one.
	if(character_current_target != noone)
	{
		instance_deactivate_object(instance);
		instance = instance_nearest(argument0.x, argument0.y, argument1);	
	}
Tendo localizado o objeto selecionado atual (ou ignorado o loop porque ele não existia), eu determino se é necessário desabilita-lo também. A lógica é a seguinte: se nenhum objeto estava selecionado, entao o primeiro objeto da fila foi escolhido lá no começo, o loop de cancelamento foi ignorado, e o primeiro objeto da fila é aquele que eu quero mesmo, então esse passo também pode ser ignorado, mas se existia algum objeto selecionado, ele é o meu objeto selecionado atual, e eu o desabilito também e seleciono o proximo do ciclo.

Código: Selecionar todos

	
	//all objects deactivated, the current selected one was the last on list
	if (instance == noone)
	{
		instance_activate_object(argument1);
		//will attempt to use the first on list
		instance = instance_nearest(argument0.x, argument0.y, argument1);
	}

Esse ultimo código verifica o ultimo caso restante, onde o objeto selecionado atual era o ultimo do ciclo, onde nesse caso todos os objetos do tipo terão sido desabilitados pelos loops anteriores e o ultimo instance_nearest vai retornar noone. Nesse caso eu imediatamente reativo todos os objetos e pego o primeiro da lista novamente, reiniciando o ciclo.

Código: Selecionar todos

	character_current_target = instance;
} until (character_current_target != character_state.is_dying);
Nesse instante, tendo determinado o proximo objeto no ciclo, eu atualizo a variavel, preparando a resposta de retorno do script, e então eu faço um último teste. Eu testo se o objeto está no estado is_dying. Na maquina de estados lógica do meu jogo, esse estado determina quando um objeto está executando a sua animação de morte, e a ponto de ser destruido. Não faz sentido selecionar um alvo que ja esta morrendo entao eu refaço o loop e pego o proximo na lista depois dele. Lembrem que tudo roda muito rapidamente dentro de um unico frame, então nao há como esperar o objeto morrer. Eu preciso passar por ele na lista manualmente.

Código: Selecionar todos

instance_activate_object(argument1);
return character_current_target;
Finalizando o codigo eu reativo todos os objetos desabilitados e retorno a variavel com o resultado. A coisa obvia a se notar é que os objetos ja podem ter sido reativados. Embora o codigo seja redundante, é boa pratica garantir que aquilo que precisa ser feito foi realmente feito. Existem varias maneiras de garantir isso, e embora exista espaço para se discutir se o codigo redundante é a melhor delas, não há o que se discutir que é definitivamente a mais simples. Considerando quão rapido é o codigo, eu penso que a perda de performance é aceitavel. K.I.S.S.

O codigo funciona exatamente igual se você quiser usar instance_furthest. De fato, você poderia até mesmo colocar ambas as funções no script, possivelmente controlando qual delas usar por meio de um terceiro argumento, e o codigo vai funcionar sem problemas.