2D isométrico: tela para coordenar as peças

9

Estou escrevendo um jogo 2D isométrico e estou tendo dificuldade em descobrir exatamente em qual bloco o cursor está. Aqui está um desenho:

onde xs e ys são coordenadas da tela (pixels), xt e yt são coordenadas do bloco, W e H são a largura e a altura do bloco em pixels, respectivamente. Minha notação para coordenadas é (y, x) que pode ser confusa, desculpe por isso.

O melhor que pude descobrir até agora é o seguinte:

int xtemp = xs / (W / 2);
int ytemp = ys / (H / 2);
int xt = (xs - ys) / 2;
int yt = ytemp + xt;

Isso parece quase correto, mas está me dando um resultado muito impreciso, dificultando a seleção de determinados ladrilhos, ou às vezes ele seleciona um ladrilho ao lado daquele em que estou tentando clicar. Não entendo o porquê e gostaria que alguém pudesse me ajudar a entender a lógica por trás disso.

Obrigado!

Asik
fonte

Respostas:

2

Para uma medida precisa, podemos considerar o seguinte:

Vamos primeiro considerar como transformar coordenadas do espaço isométrico, determinado pelos vetores iej (como em isometricMap [i, j]) ou como yt e xt na tela, no espaço da tela, determinado por xey da tela. Vamos supor que o espaço da tela esteja alinhado na origem com o espaço isométrico por uma questão de simplicidade.

Uma maneira de fazer a transformação é fazer uma rotação primeiro e depois escalar o eixo y ou x. Para obter os valores necessários para corresponder ao seu yt e xt, não consigo chegar exatamente aqui. Você pode criar uma matriz para fazer isso ou não e depois usar a matriz reversa, mas a operação reversa é basicamente o que você deseja.

Escale o valor ao contrário e gire para trás para obter os valores e arredondar para baixo.

Acho que existem outras maneiras de fazer isso, mas isso me parece mais apropriado agora.

Toni
fonte
argh. Estive revisando este post tantas vezes e acho que não consigo entender meu ponto de vista da maneira mais adequada possível. Eu preciso dormir.
Toni
11
Obrigado, matrizes é definitivamente a melhor solução aqui. Eu tenho algo quase funcionando agora!
Asik
4

Eu tive esse mesmo problema em um jogo que estava escrevendo. Eu imagino que esse problema será diferente com base em como exatamente você implementou seu sistema isométrico, mas explicarei como resolvi o problema.

Comecei com minha função tile_to_screen. (Suponho que é assim que você coloca os ladrilhos no local certo em primeiro lugar.) Essa função tem uma equação para calcular screen_x e screen_y. O meu ficou assim (python):

def map_to_screen(self, point):
    x = (SCREEN_WIDTH + (point.y - point.x) * TILE_WIDTH) / 2
    y = (SCREEN_HEIGHT + (point.y + point.x) * TILE_HEIGHT) / 2
    return (x, y)

Peguei essas duas equações e as transformei em um sistema de equações lineares. Resolva esse sistema de equações em qualquer método que você escolher. (Eu usei um método rref. Além disso, algumas calculadoras gráficas podem resolver esse problema.)

As equações finais eram assim:

# constants for quick calculating (only process once)
DOUBLED_TILE_AREA = 2 * TILE_HEIGHT * TILE_WIDTH
S2M_CONST_X = -SCREEN_HEIGHT * TILE_WIDTH + SCREEN_WIDTH * TILE_HEIGHT
S2M_CONST_Y = -SCREEN_HEIGHT * TILE_WIDTH - SCREEN_WIDTH * TILE_HEIGHT

def screen_to_map(self, point):
    # the "+ TILE_HEIGHT/2" adjusts for the render offset since I
    # anchor my sprites from the center of the tile
    point = (point.x * TILE_HEIGHT, (point.y + TILE_HEIGHT/2) * TILE_WIDTH)
    x = (2 * (point.y - point.x) + self.S2M_CONST_X) / self.DOUBLED_TILE_AREA
    y = (2 * (point.x + point.y) + self.S2M_CONST_Y) / self.DOUBLED_TILE_AREA
    return (x, y)

Como você pode ver, não é simples como a equação inicial. Mas funciona muito bem para o jogo que criei. Graças a Deus pela álgebra linear!

Atualizar

Depois de escrever uma classe Point simples com vários operadores, simplifiquei esta resposta para o seguinte:

# constants for quickly calculating screen_to_iso
TILE_AREA = TILE_HEIGHT * TILE_WIDTH
S2I_CONST_X = -SCREEN_CENTER.y * TILE_WIDTH + SCREEN_CENTER.x * TILE_HEIGHT
S2I_CONST_Y = -SCREEN_CENTER.y * TILE_WIDTH - SCREEN_CENTER.x * TILE_HEIGHT

def screen_to_iso(p):
    ''' Converts a screen point (px) into a level point (tile) '''
    # the "y + TILE_HEIGHT/2" is because we anchor tiles by center, not bottom
    p = Point(p.x * TILE_HEIGHT, (p.y + TILE_HEIGHT/2) * TILE_WIDTH)
    return Point(int((p.y - p.x + S2I_CONST_X) / TILE_AREA),
                 int((p.y + p.x + S2I_CONST_Y) / TILE_AREA))

def iso_to_screen(p):
    ''' Converts a level point (tile) into a screen point (px) '''
    return SCREEN_CENTER + Point((p.y - p.x) * TILE_WIDTH / 2,
                                 (p.y + p.x) * TILE_HEIGHT / 2)
Thane Brimhall
fonte
Sim, um sistema de duas equações lineares também deve funcionar. Considerando que temos dois vetores que não são paralelos, você poderá obter qualquer ponto no plano usando os vetores unitários de yt e xt. Embora eu ache que sua implementação seja um pouco restrita e não vou me dar ao trabalho de validá-la.
Toni
2

Você está usando um bom sistema de coordenadas. As coisas ficam muito mais complicadas se você usar colunas escalonadas.

Uma maneira de pensar sobre esse problema é que você tem uma função para transformar (xt, yt) em (xs, ys). Vou seguir a resposta de Thane e ligar map_to_screen.

Você quer o inverso dessa função. Nós podemos chamá-lo screen_to_map. Inversos de funções têm estas propriedades:

map_to_screen(screen_to_map(xs, ys)) == (xs, ys)
screen_to_map(map_to_screen(xt, yt)) == (xt, yt)

Essas duas coisas são boas para o teste de unidade depois que você tiver as duas funções escritas. Como você escreve o inverso? Nem todas as funções têm inversas, mas neste caso:

  1. Se você o escreveu como uma rotação seguida por uma tradução, o inverso é a tradução inversa (dx negativo, dy) seguida pela rotação inversa (ângulo negativo).
  2. Se você o escreveu como uma multiplicação de matrizes, o inverso é a multiplicação inversa de matrizes.
  3. Se você o escreveu como equações algébricas que definem (xs, ys) em termos de (xt, yt), o inverso é encontrado resolvendo essas equações para (xt, yt) dados (xs, ys).

Certifique-se de testar se a função inversa + original retorna a resposta com a qual você começou. Thane passa nos dois testes, se você remover o + TILE_HEIGHT/2deslocamento de renderização. Quando resolvi a álgebra, criei:

x = (2*xs - SCREEN_WIDTH) / TILE_WIDTH
y = (2*ys - SCREEN_HEIGHT) / TILE_HEIGHT
yt =  (y + x) / 2
xt =  (y - x) / 2

que eu acredito que é o mesmo que Thane screen_to_map.

A função transformará as coordenadas do mouse em flutuadores; use floorpara convertê-los em coordenadas de bloco inteiro.

amitp
fonte
11
Obrigado! Acabei usando uma matriz de transformação, para que escrever o inverso seja trivial, ou seja, apenas Matrix.Invert (). Além disso, leva a um estilo de codificação mais declarativo (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate () em vez de várias equações). Talvez seja um pouco mais lento, mas isso não deve ser um problema.
Asik