Problema de detecção de colisão de mapa com base em mosaico

7

Estou trabalhando em um clone do Mario baseado em blocos.

Isso funciona muito bem ao caminhar e cair. Mas quando o jogador pula perto de uma parede e caminha para a direita no ar, ele fica preso na parede. O sprite do jogador cai novamente quando o jogador libera a chave.

A instalação é bem fácil e não consigo encontrar o problema. O mapa é construído como uma matriz 2D com blocos de mapa. Um bloco pode ser sólido ou não. O jogador não pode mover objetos sólidos, duh ..

No ciclo do jogo:

  • a localização do jogador é atualizada (gravidade, movimento etc.)
  • verifique o mapa em colisões. Quando uma colisão é encontrada no Y, a localização do jogador é atualizada, ficando acima ou abaixo do bloco (dependendo da direção do jogador), a caixa de colisão é atualizada com a nova localização. Em seguida, o mesmo processo para o X.
  • A caixa de colisão é atualizada para o novo local (um local gratuito). A caixa é ajustada para que seja um pouco mais alto colocar o bloco abaixo do jogador para verificar se ele caiu. Isso é para mudar o estado do jogador, de sprite voador para ect ocioso.

Também tentei mudar a verificação X e Y para que o jogador fosse movido na linha X. Então, quando o jogador se move, o movimento fica muito lento. Quando pressiono e solto o botão para mover, o jogador se move mais rápido, mas em pontos. Muito trippy ..

Alguém vê o erro ou pode me dar um algoritmo de colisão melhor para isso?

UPDATE (não atualizou o código)

Troquei o método de verificação xey e implementei a variável isonland. Então, quando andar e pular contra paredes funciona perfeitamente. Só agora, quando o jogador pula, Mario volta atrás quando pousa. Isso porque o método X check vai primeiro e ajusta a posição de Mario.

Como posso resolver isso?

Método de atualização da classe de mapa:

public void update(int timeElapsed) {
        //update entities
        for(Entity entity : _mapEntities) {
            entity.update(timeElapsed);
        }

        //update objects
        for(MapObject mapObt : _mapObjects) {
            mapObt.update(timeElapsed);
        }

        //check for collisions
        checkMapCollision();
    }

Método de atualização da entidade (abstrato):

public void update(int timeElapsed) {
        _velocity = new Vector2d(0.0F, 0.0F);

        //add gravity
        _velocity.y = Map._GRAVITY_PER_SEC * timeElapsed;
    }

Mario (estende a entidade) atualizar methos:

@Override
    public void update(int timeElapsed) {
        super.update(timeElapsed);

        if(_state == STATES.IDLE) {

        } else if(_isMoving) {
            _marioSmallWalk.update(timeElapsed);
        }

        if(_state == STATES.JUMPING) {
            setVelocityY(getVelocity().y + _jumpSpeed);

            _jumpSpeed += _JUMP_DECREASE * timeElapsed;

            //falling?
            if(getVelocity().y > 0) {
                setState(STATES.FALLING);
            }
        } 

        if(_isMoving) {
            double walkSpd = (_WALK_SPEED_SEC * timeElapsed);

            if(getFacing() == FACING.LEFT) {
                walkSpd = -walkSpd;
            }

            setVelocityX(getVelocity().x + walkSpd);
        }

        //falling?
        if(getVelocity().y > (Map._GRAVITY_PER_SEC * timeElapsed) + 1.0F) {
            setState(STATES.FALLING);
        }

        setPosition((int)(getX() + getVelocity().x), (int)(getY() + getVelocity().y));
    }

Classe de mapa método CheckMapCollision:

public void checkMapCollision() {
        //enteties move so check it
        for(Entity entity : _mapEntities) {
            //get the corners
            Rectangle bounds = entity.getBounds();
            Block[] corners = getCornerBlocks(bounds);
            Vector2d dir = entity.getDirection();

            //moving down
            if(dir.y > 0) {
                if(corners[2].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[2].isSolid()) {
                        blkBounds = corners[2].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.setPositionY(blkBounds.y);
                }
            } else {
                if(corners[0].isSolid() || corners[1].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[0].isSolid()) {
                        blkBounds = corners[0].getBounds();
                    } else {
                        blkBounds = corners[1].getBounds();
                    }

                    entity.setPositionY(blkBounds.y + blkBounds.height + bounds.height);
                }
            }

            bounds = entity.getBounds();
            corners = getCornerBlocks(bounds);

            //moving to the right
            if(dir.x > 0) {
                if(corners[1].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds;

                    if(corners[1].isSolid()) {
                        blkBounds = corners[1].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.setPositionX(blkBounds.x - (bounds.width-entity.getCurrentSprite().getOffsetX())-1);
                }
            } else {
                if(corners[0].isSolid() || corners[2].isSolid()) {
                    Rectangle blkBounds;

                    if(corners[0].isSolid()) {
                        blkBounds = corners[0].getBounds();
                    } else {
                        blkBounds = corners[2].getBounds();
                    }

                    entity.setPositionX(blkBounds.x + blkBounds.width + (bounds.width/2));
                }
            }

            bounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height+1);
            corners = getCornerBlocks(bounds);

            //moving down
            if(dir.y > 0) {
                if(corners[2].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[2].isSolid()) {
                        blkBounds = corners[2].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.landed();
                    System.out.println("landed");
                }
            }
        }
    }
Sven van Zoelen
fonte
Você tem certeza de que, após detectar uma colisão, está reagindo a ela? Parece que o personagem não está sendo retirado do objeto e, em vez disso, está sendo parado onde está (mas eu não li o código completamente).
James
Sim, eu tenho certeza disso. O entity.setPositionX()ou entity.setPositionY()é chamado após a verificação de colisão. Quando ando contra a parede sem pular, o jogador é empurrado de volta corretamente.
Sven van Zoelen

Respostas:

8

Seu personagem sprite fica preso nas interseções entre duas peças. Veja o diagrama a seguir, que descreve a situação no início de sua rotina de colisão.

Colisão com uma parede

Mario foi movido para a direita devido aos controles do jogador. Isso colocará o sprite nas três peças. Então, você realiza a detecção de colisão em Y descendente. Observe o ponto no círculo azul: isso detectará uma colisão com o segundo bloco e tentará resolvê-lo, definindo a coordenada Y no limite superior desse bloco, suspendendo efetivamente Mario em no ar.

Assim que o jogador soltar a chave para mover Mario para a direita, o sprite não se moverá contra a parede e o ponto que anteriormente detectaria uma colisão não registrará um acerto. Isso permitirá que Mario caia no chão.

A solução rápida e suja para esse problema é resolver as colisões de X primeiro e aplicar a gravidade apenas quando o jogador não estiver no chão. Se você mantivesse a gravidade, o mesmo problema ocorreria ao andar por aí (esse é o comportamento 'trippy' que você já viu depois de trocar a verificação X e Y;)).

fantasma
fonte
Obrigado! Troquei a verificação xey e implementei uma verificação isOnLand. Assim, o jogador pode pular e deslizar contra a parede. Agora surge outro problema: quando o jogador fica ao lado da parede e pula, quando ele pousa o x é ajustado (da colisão no chão) e o jogador é colocado na parede.
Sven van Zoelen
Acho difícil imaginar a possível estrutura de código com base no seu comentário. Por que X precisa ser ajustado quando Mario colide com o chão após um salto? Eu acho que isso só precisará de um ajuste em Y, deixando X intocado.
fantasma
1

Eu descobri o que estava fazendo de errado.

A chave está quando você atualiza a posição. O que fiz de errado foi que fiz o método 'mudar de posição primeiro e depois verificar e ajustar', e tive que fazer isso na ordem, verificar e atualizar.

Meu código anterior fez o seguinte:

  • atualizar localizações de entidades com velocidade.
  • verifique se há colisões e atualize os locais das entidades.

Isso resultou em que, depois que o jogador pulou, a localização de Mario foi alterada no método de atualização para que a caixa de colisão ficasse nos ladrilhos no chão. Em seguida, o método de colisão alterava a posição X para x no canto inferior esquerdo e então o movia para cima do bloco do solo. Isso significa que Mario volta alguns pixels toda vez que o jogador aterrissa de um salto.

Eu tentei trocar os métodos de verificação X e Y, mas o problema muda para outra coisa (o abraço na parede).

Agora a solução, eu mudei para o seguinte:

  • atualizar a velocidade das entidades.
  • método de colisão
    • tente colocar a entidade no X com a velocidade, se falhar, altere-a da melhor maneira possível.
    • tente colocar a entidade no Y com a velocidade, se falhar, altere-a da melhor maneira possível.

Agora, o X é verificado / alterado primeiro e, em seguida, a caixa de colisão é atualizada com o novo local da entidade. Então o Y é verificado / alterado.

Isso funciona como um encanto :)

Sven van Zoelen
fonte
Voce pode dar alguns exemplos? É difícil entender o que você fez
Gyozo kudor