É uma má idéia "mapear" a posição do mouse na tela para que a detecção de colisão funcione independentemente da resolução?

16

Considere um jogo cuja resolução padrão seja 800x600. Objetos com máscaras de colisão são colocados em um mundo de jogo de tamanho 800x600. As máscaras de colisão podem detectar quando o mouse colide com elas.

Agora considere escalar o jogo em 1024x768 (suponha que escalemos os gráficos simplesmente renderizando tudo em uma camada e depois escalando a camada inteira de uma só vez). Temos duas opções para fazer com que as colisões com o mouse funcionem corretamente nesta nova resolução:

A.) Escale o mundo para 1024x768 e dimensione a máscara de colisão de cada objeto de acordo.

B.) "Mapeie" a posição do mouse no mundo original (800x600).

Por "mapa", quero dizer, basta escalar a posição do mouse no mundo original de 800x600. Por exemplo, se a posição do mouse na tela for (1024, 768), a posição do mouse no mundo será (800, 600).

Agora, obviamente, a opção B requer muito menos computação e provavelmente é menos propensa a erros geométricos, mas também parece meio "hack" para mim, como se houvesse conseqüências imprevistas de seguir esse método que serão um inferno para corrigir mais tarde.

Com qual método devo ir: A, B ou outra coisa?

user3002473
fonte
2
Conforme apontado pelo Classic Thunder, as coordenadas de exibição não devem ser as mesmas que as coordenadas do mundo ou do objeto. Normalmente você deseja ter transformações (basicamente, como você diz mapeamentos , mas geralmente feito com matemática matricial).
Dan

Respostas:

38

Normalmente (mesmo para jogos em 2D), existe um sistema de coordenadas separado para o jogo, que é independente da resolução, geralmente chamado de espaço no mundo. Isso permite que você dimensione para uma resolução arbitrária.

A maneira mais fácil de conseguir isso é usar uma câmera 2D, que é essencialmente uma matriz que define a transição do espaço do mundo (suas unidades arbitrárias) para o espaço da tela (os pixels na tela). Isso torna trivial lidar com a matemática.

Dê uma olhada na seção abaixo em Rolagem de câmera XNA 2d - por que usar a transformação de matriz?

Facilita a conversão entre as definições do sistema de coordenadas

Para ir da tela para o espaço mundial, basta usar Vector2.Transform. Isso é comumente usado para obter a localização do mouse no mundo para a seleção de objetos.

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

Para ir do mundo para o espaço da tela, basta fazer o oposto.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Não há desvantagens em usar uma matriz que não seja um pouco de aprendizado.

ClassicThunder
fonte
... e de fato, de um modo geral, o hardware moderno é projetado para operações de matriz por meio de arquiteturas de vetorização como SSE, NEON, etc. Portanto, é a escolha inteligente - você economizará ciclos de CPU em abordagens não vetorizadas.
Engenheiro de
11
Isenção de responsabilidade: O XNA foi descontinuado pela microsoft, mas o Monogame usa a mesma API.
Pharap
11
Claro, é inteligente usar matrizes para traduzir entre os dois sistemas de coordenadas. Mas como isso responde à pergunta? Você ainda deve decidir se deseja mapear do sistema A para o sistema B ou de B para A e quais são as consequências, se houver alguma.
Mašťov
"É uma má idéia" mapear "a posição do mouse na tela para que a detecção de colisão funcione independentemente da resolução?" é respondida por "é inteligente para usar matrizes para traduzir entre os dois sistemas de coordenadas" ...
ClassicThunder
Também respondo: "Você ainda deve decidir se deseja mapear do sistema A para o sistema B ou de B para A e quais são as consequências, se houver alguma". dizendo que você deve usar um arbitrário que não esteja vinculado à resolução e à escala dela. Então, C -> A ou C -> B, dependendo de A ou B ser a resolução. É claro que você pode ter C igual a uma resolução básica que você projeta e dimensiona a partir daí. O ponto é que toda a matemática acontece no mesmo sistema de coordenadas e você apenas escala para a renderização.
precisa
2

Outra opção seria: Em cada evento de movimento do mouse de entrada, mova o cursor do mouse no jogo pelo número de pixels do jogo correspondente ao número de pixels no evento do mouse. Isso ocorre naturalmente em jogos 3D que bloqueiam o ponteiro real do mouse no centro e giram a direção da mira em uma quantidade correspondente ao movimento do mouse de entrada, mas você pode fazer o mesmo movendo um sprite representando o cursor do mouse no jogo.

Obviamente, você deve ignorar qualquer evento de movimento do mouse causado por deformar o objeto para o centro, e se o seu jogo apresentar algum atraso de entrada, a falta de resposta do cursor será o aspecto mais perturbador e perceptível.

Parcialmente, qual solução você usa depende da importância da posição do mouse na jogabilidade. Se este é um RTS e o jogador está simplesmente clicando para selecionar as unidades, você provavelmente pode simplesmente optar pelo que for mais fácil para o seu A ou B. Se é um jogo de tiro de cima para baixo e o mouse controla diretamente os movimentos do personagem, você provavelmente desejará uma solução mais profunda que limite a quantidade de variabilidade na maneira como o personagem pode se mover com base não apenas na resolução, mas também em movimentos do mouse Rapidez. Se o mouse controlar a direção da segmentação, você desejará uma solução diferente etc.

Random832
fonte
0

A resposta do ClassicThunder está correta, mas eu gostaria de fornecer um exemplo de um meio alternativo / mais simples de obter o efeito desejado. Esta é uma solução mais simples para prototipagem rápida, casos em que você não tem acesso a uma biblioteca com todos os recursos ou casos em que você não tem acesso a uma GPU (por exemplo, em sistemas embarcados).

Para executar o referido mapeamento, você pode usar a seguinte função (suponha que ela seja definida na classe estática Helper):

static float Map(float value, float fromLow, float fromHigh, float toLow, float toHigh)
{
    return ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow)) + toLow;
}

(Você não especificou um idioma, mas vejo que você tem algum conhecimento de C #, então meu exemplo está em C #.)

Você pode usar esta função da seguinte maneira:

float mouseXInWorld = Helper.Map(Mouse.X, 0, Screen.Width - 1, camera.Bounds.X, camera.Bounds.X + camera.Bounds.Width - 1);
float mouseYInWorld = Helper.Map(Mouse.Y, 0, Screen.Height - 1, camera.Bounds.Y, camera.Bounds.Y + camera.Bounds.Height - 1);

Onde camera.Bounds está um retângulo representando a área do mundo que a câmera pode ver (ou seja, a área sendo projetada na tela).

Se você tiver uma classe Vectorou Point, poderá simplificar ainda mais esse processo criando um equivalente em 2D da função de mapa, assim:

static Vector Map(Vector value, Rectangle fromArea, Rectangle toArea)
{
    Vector result = new Vector();
    result.X = Map(value.X, fromArea.X, fromArea.X + fromArea.Width - 1, toArea.X, toArea.X + toArea.Width - 1);
    result.Y = Map(value.Y, fromArea.Y, fromArea.Y + fromArea.Height - 1, toArea.Y, toArea.Y + toArea.Height - 1);
    return result;
}

O que tornaria seu código de mapeamento um liner simples:

Vector mousePosInWorld = Map(Mouse.Pos, Screen.Bounds, camera.Bounds);
Pharap
fonte
-1

Opção (C): altere a resolução da tela para 800x600.

Mesmo se você não fizer isso, considere isso como um experimento mental. Nesse caso, é de responsabilidade do monitor redimensionar os gráficos para ajustá-los ao tamanho físico e, em seguida, de responsabilidade do sistema operacional fornecer eventos de ponteiro com uma resolução de 800x600.

Eu acho que tudo depende se seus gráficos são bitmap ou vetor. Se eles são bitmap e você está renderizando para um buffer de 800x600, sim, é muito mais fácil remapear o mouse no espaço da tela e ignorar a resolução real da tela. No entanto, a grande desvantagem disso é que o upscaling pode parecer feio, especialmente se você estiver criando gráficos em estilo "8 bits".

pjc50
fonte